1 /*
2 * Copyright 2008-2013 Various Authors
3 * Copyright 2004-2005 Timo Hirvonen
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "../op.h"
20 #include "../sf.h"
21 #include "../xmalloc.h"
22 #include "../debug.h"
23 #include "../utils.h"
24
25 #if defined(__OpenBSD__)
26 #include <soundcard.h>
27 #else
28 #include <sys/soundcard.h>
29 #endif
30 #include <sys/ioctl.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33 #include <unistd.h>
34 #include <fcntl.h>
35
36 static sample_format_t oss_sf;
37 static int oss_fd = -1;
38
39 /* configuration */
40 static char *oss_dsp_device = NULL;
41
42 static int oss_close(void);
43
oss_reset(void)44 static int oss_reset(void)
45 {
46 if (fcntl(oss_fd, SNDCTL_DSP_RESET, 0) == -1) {
47 return -1;
48 }
49 return 0;
50 }
51
52 #if defined(__linux__)
53 /* defined only in OSSv4, but seem to work in OSSv3 (Linux) */
54 #ifndef AFMT_S32_LE
55 #define AFMT_S32_LE 0x00001000
56 #endif
57 #ifndef AFMT_S32_BE
58 #define AFMT_S32_BE 0x00002000
59 #endif
60 #ifndef AFMT_S24_PACKED
61 #define AFMT_S24_PACKED 0x00040000
62 #endif
63 #endif
64
65 struct oss_fmt {
66 int fmt, bits, sig, be;
67 };
68 static struct oss_fmt oss_fmts[] = {
69 { AFMT_S16_BE, 16, 1, 1 },
70 { AFMT_S16_LE, 16, 1, 0 },
71 #ifdef AFMT_S24_PACKED
72 { AFMT_S24_PACKED, 24, 1, 0 },
73 #endif
74 #ifdef AFMT_S24_BE
75 { AFMT_S24_BE, 24, 1, 1 },
76 #endif
77 #ifdef AFMT_S24_LE
78 { AFMT_S24_LE, 24, 1, 0 },
79 #endif
80 #ifdef AFMT_S32_BE
81 { AFMT_S32_BE, 32, 1, 1 },
82 #endif
83 #ifdef AFMT_S32_LE
84 { AFMT_S32_LE, 32, 1, 0 },
85 #endif
86
87 { AFMT_U16_BE, 16, 0, 1 },
88 { AFMT_U16_LE, 16, 0, 0 },
89 #ifdef AFMT_U24_BE
90 { AFMT_U24_BE, 24, 0, 1 },
91 #endif
92 #ifdef AFMT_U24_LE
93 { AFMT_U24_LE, 24, 0, 0 },
94 #endif
95 #ifdef AFMT_U32_BE
96 { AFMT_U32_BE, 32, 0, 1 },
97 #endif
98 #ifdef AFMT_U32_LE
99 { AFMT_U32_LE, 32, 0, 0 },
100 #endif
101 { AFMT_S8, 8, 1, 0 },
102 { AFMT_S8, 8, 1, 1 },
103 { AFMT_U8, 8, 0, 0 },
104 { AFMT_U8, 8, 0, 1 },
105 };
106
oss_set_sf(sample_format_t sf)107 static int oss_set_sf(sample_format_t sf)
108 {
109 int found, tmp, log2_fragment_size, nr_fragments, bytes_per_second;
110 size_t i;
111
112 oss_reset();
113 oss_sf = sf;
114
115 #ifdef SNDCTL_DSP_CHANNELS
116 tmp = sf_get_channels(oss_sf);
117 if (ioctl(oss_fd, SNDCTL_DSP_CHANNELS, &tmp) == -1)
118 return -1;
119 #else
120 tmp = sf_get_channels(oss_sf) - 1;
121 if (ioctl(oss_fd, SNDCTL_DSP_STEREO, &tmp) == -1)
122 return -1;
123 #endif
124
125 found = 0;
126 for (i = 0; i < N_ELEMENTS(oss_fmts); i++) {
127 if (sf_get_bits(oss_sf) == oss_fmts[i].bits &&
128 sf_get_signed(oss_sf) == oss_fmts[i].sig &&
129 sf_get_bigendian(oss_sf) == oss_fmts[i].be) {
130 found = 1;
131 tmp = oss_fmts[i].fmt;
132 break;
133 }
134 }
135 if (!found) {
136 d_print("unsupported sample format: %c%u_%s\n",
137 sf_get_signed(oss_sf) ? 'S' : 'U', sf_get_bits(oss_sf),
138 sf_get_bigendian(oss_sf) ? "BE" : "LE");
139 return -1;
140 }
141 if (ioctl(oss_fd, SNDCTL_DSP_SAMPLESIZE, &tmp) == -1)
142 return -1;
143
144 tmp = sf_get_rate(oss_sf);
145 if (ioctl(oss_fd, SNDCTL_DSP_SPEED, &tmp) == -1)
146 return -1;
147
148 bytes_per_second = sf_get_second_size(oss_sf);
149 log2_fragment_size = 0;
150 while (1 << log2_fragment_size < bytes_per_second / 25)
151 log2_fragment_size++;
152 log2_fragment_size--;
153 nr_fragments = 32;
154
155 /* bits 0..15 = size of fragment, 16..31 = log2(number of fragments) */
156 tmp = (nr_fragments << 16) + log2_fragment_size;
157 if (ioctl(oss_fd, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
158 return -1;
159 return 0;
160 }
161
oss_device_exists(const char * device)162 static int oss_device_exists(const char *device)
163 {
164 struct stat s;
165
166 if (stat(device, &s))
167 return 0;
168 return 1;
169 }
170
oss_init(void)171 static int oss_init(void)
172 {
173 const char *new_dsp_dev = "/dev/sound/dsp";
174 const char *dsp_dev = "/dev/dsp";
175
176 if (oss_dsp_device) {
177 if (oss_device_exists(oss_dsp_device))
178 return 0;
179 free(oss_dsp_device);
180 oss_dsp_device = NULL;
181 return -1;
182 }
183 if (oss_device_exists(new_dsp_dev)) {
184 oss_dsp_device = xstrdup(new_dsp_dev);
185 return 0;
186 }
187 if (oss_device_exists(dsp_dev)) {
188 oss_dsp_device = xstrdup(dsp_dev);
189 return 0;
190 }
191 return -1;
192 }
193
oss_exit(void)194 static int oss_exit(void)
195 {
196 free(oss_dsp_device);
197 oss_dsp_device = NULL;
198 return 0;
199 }
200
oss_open(sample_format_t sf,const channel_position_t * channel_map)201 static int oss_open(sample_format_t sf, const channel_position_t *channel_map)
202 {
203 int oss_version = 0;
204 oss_fd = open(oss_dsp_device, O_WRONLY);
205 if (oss_fd == -1)
206 return -1;
207 ioctl(oss_fd, OSS_GETVERSION, &oss_version);
208 d_print("oss version: %#08x\n", oss_version);
209 if (oss_set_sf(sf) == -1) {
210 oss_close();
211 return -1;
212 }
213 return 0;
214 }
215
oss_close(void)216 static int oss_close(void)
217 {
218 close(oss_fd);
219 oss_fd = -1;
220 return 0;
221 }
222
oss_write(const char * buffer,int count)223 static int oss_write(const char *buffer, int count)
224 {
225 int rc;
226
227 count -= count % sf_get_frame_size(oss_sf);
228 rc = write(oss_fd, buffer, count);
229 return rc;
230 }
231
oss_pause(void)232 static int oss_pause(void)
233 {
234 if (ioctl(oss_fd, SNDCTL_DSP_POST, NULL) == -1)
235 return -1;
236 return 0;
237 }
238
oss_unpause(void)239 static int oss_unpause(void)
240 {
241 return 0;
242 }
243
oss_buffer_space(void)244 static int oss_buffer_space(void)
245 {
246 audio_buf_info info;
247 int space;
248
249 if (ioctl(oss_fd, SNDCTL_DSP_GETOSPACE, &info) == -1)
250 return -1;
251 space = (info.fragments - 1) * info.fragsize;
252 return space;
253 }
254
op_oss_set_device(const char * val)255 static int op_oss_set_device(const char *val)
256 {
257 free(oss_dsp_device);
258 oss_dsp_device = xstrdup(val);
259 return 0;
260 }
261
op_oss_get_device(char ** val)262 static int op_oss_get_device(char **val)
263 {
264 if (oss_dsp_device)
265 *val = xstrdup(oss_dsp_device);
266 return 0;
267 }
268
269 const struct output_plugin_ops op_pcm_ops = {
270 .init = oss_init,
271 .exit = oss_exit,
272 .open = oss_open,
273 .close = oss_close,
274 .write = oss_write,
275 .pause = oss_pause,
276 .unpause = oss_unpause,
277 .buffer_space = oss_buffer_space,
278 };
279
280 const struct output_plugin_opt op_pcm_options[] = {
281 OPT(op_oss, device),
282 { NULL },
283 };
284
285 const int op_priority = 1;
286 const unsigned op_abi_version = OP_ABI_VERSION;
287