1 /*
2 * Copyright (c) 2012 Tim van der Molen <tim@kariliq.nl>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <sys/ioctl.h>
18 #include <sys/types.h>
19
20 #include <fcntl.h>
21 #include <stdlib.h>
22 #include <unistd.h>
23
24 #include "../siren.h"
25
26 #ifdef HAVE_SYS_SOUNDCARD_H
27 #include <sys/soundcard.h>
28 #else
29 #include <soundcard.h>
30 #endif
31
32 #if defined(SNDCTL_DSP_GETPLAYVOL) && defined(SNDCTL_DSP_SETPLAYVOL)
33 #define OP_OSS_HAVE_VOLUME_SUPPORT
34 #endif
35
36 #define OP_OSS_BUFSIZE 4096
37 #define OP_OSS_DEVICE "/dev/dsp"
38
39 static void op_oss_close(void);
40 static size_t op_oss_get_buffer_size(void);
41 static int op_oss_get_volume_support(void);
42 static int op_oss_init(void);
43 static int op_oss_open(void);
44 static int op_oss_start(struct sample_format *);
45 static int op_oss_stop(void);
46 static int op_oss_write(struct sample_buffer *);
47 #ifdef OP_OSS_HAVE_VOLUME_SUPPORT
48 static int op_oss_get_volume(void);
49 static void op_oss_set_volume(unsigned int);
50 #endif
51
52 struct op op = {
53 "oss",
54 OP_PRIORITY_OSS,
55 NULL,
56 op_oss_close,
57 op_oss_get_buffer_size,
58 #ifdef OP_OSS_HAVE_VOLUME_SUPPORT
59 op_oss_get_volume,
60 #else
61 NULL,
62 #endif
63 op_oss_get_volume_support,
64 op_oss_init,
65 op_oss_open,
66 #ifdef OP_OSS_HAVE_VOLUME_SUPPORT
67 op_oss_set_volume,
68 #else
69 NULL,
70 #endif
71 op_oss_start,
72 op_oss_stop,
73 op_oss_write
74 };
75
76 static size_t op_oss_buffer_size;
77 static int op_oss_fd;
78 static char *op_oss_device;
79 #ifdef OP_OSS_HAVE_VOLUME_SUPPORT
80 static int op_oss_volume;
81 #endif
82
83 static void
op_oss_close(void)84 op_oss_close(void)
85 {
86 free(op_oss_device);
87 }
88
89 /* Return the buffer size in bytes. */
90 static size_t
op_oss_get_buffer_size(void)91 op_oss_get_buffer_size(void)
92 {
93 return op_oss_buffer_size;
94 }
95
96 #ifdef OP_OSS_HAVE_VOLUME_SUPPORT
97 static int
op_oss_get_volume(void)98 op_oss_get_volume(void)
99 {
100 int arg;
101
102 /* If the device hasn't been opened, then return the saved volume. */
103 if (op_oss_fd == -1)
104 return op_oss_volume;
105
106 if (ioctl(op_oss_fd, SNDCTL_DSP_GETPLAYVOL, &arg) == -1) {
107 LOG_ERR("ioctl: SNDCTL_DSP_GETPLAYVOL");
108 msg_err("Cannot get volume");
109 return -1;
110 }
111
112 /*
113 * The two least significant bytes contain the volume levels for the
114 * left and the right channel, respectively. The two levels should have
115 * the same value, so we can use either one. The range is from 0 to 100
116 * inclusive.
117 */
118 return arg & 0xff;
119 }
120 #endif
121
122 static int
op_oss_get_volume_support(void)123 op_oss_get_volume_support(void)
124 {
125 #ifdef OP_OSS_HAVE_VOLUME_SUPPORT
126 return op_oss_volume == -1 ? 0 : 1;
127 #else
128 return 0;
129 #endif
130 }
131
132 static int
op_oss_init(void)133 op_oss_init(void)
134 {
135 option_add_string("oss-device", OP_OSS_DEVICE, player_reopen_op);
136 return 0;
137 }
138
139 static int
op_oss_open(void)140 op_oss_open(void)
141 {
142 op_oss_device = option_get_string("oss-device");
143 LOG_INFO("using device %s", op_oss_device);
144
145 #ifdef OP_OSS_HAVE_VOLUME_SUPPORT
146 op_oss_fd = open(op_oss_device, O_WRONLY);
147 if (op_oss_fd == -1) {
148 LOG_ERR("open: %s", op_oss_device);
149 msg_err("Cannot open %s", op_oss_device);
150 free(op_oss_device);
151 return -1;
152 }
153
154 op_oss_volume = op_oss_get_volume();
155 close(op_oss_fd);
156 op_oss_fd = -1;
157 #endif
158
159 return 0;
160 }
161
162 #ifdef OP_OSS_HAVE_VOLUME_SUPPORT
163 static void
op_oss_set_volume(unsigned int volume)164 op_oss_set_volume(unsigned int volume)
165 {
166 int arg;
167
168 if (op_oss_fd == -1)
169 msg_errx("Cannot change the volume level while the device is "
170 "closed");
171 else {
172 /* Set the volume level for the left and right channels. */
173 arg = volume | (volume << 8);
174 if (ioctl(op_oss_fd, SNDCTL_DSP_SETPLAYVOL, &arg) == -1) {
175 LOG_ERR("ioctl: SNDCTL_DSP_SETPLAYVOL");
176 msg_err("Cannot set volume");
177 }
178 }
179 }
180 #endif
181
182 static int
op_oss_start(struct sample_format * sf)183 op_oss_start(struct sample_format *sf)
184 {
185 int arg, want_arg;
186
187 op_oss_fd = open(op_oss_device, O_WRONLY);
188 if (op_oss_fd == -1) {
189 LOG_ERR("open: %s", op_oss_device);
190 msg_err("Cannot open %s", op_oss_device);
191 return -1;
192 }
193
194 /*
195 * The OSS 4 documentation recommends to set the number of channels
196 * first, then the sample format and then the sampling rate.
197 */
198
199 /* Set number of channels. */
200 arg = sf->nchannels;
201 if (ioctl(op_oss_fd, SNDCTL_DSP_CHANNELS, &arg) == -1) {
202 LOG_ERR("ioctl: SNDCTL_DSP_CHANNELS");
203 msg_err("Cannot set number of channels");
204 goto error;
205 }
206 if (arg != (int)sf->nchannels) {
207 LOG_ERRX("%u channels not supported", sf->nchannels);
208 msg_errx("%u channels not supported", sf->nchannels);
209 goto error;
210 }
211
212 /* Set format. */
213 if (sf->nbits <= 8)
214 arg = AFMT_S8;
215 else if (sf->nbits <= 16)
216 arg = AFMT_S16_NE;
217 else {
218 #ifdef AFMT_S32_NE
219 arg = AFMT_S32_NE;
220 #else
221 LOG_ERRX("%u bits per sample not supported", sf->nbits);
222 msg_errx("%u bits per sample not supported", sf->nbits);
223 goto error;
224 #endif
225 }
226
227 want_arg = arg;
228 if (ioctl(op_oss_fd, SNDCTL_DSP_SETFMT, &arg) == -1) {
229 LOG_ERR("ioctl: SNDCTL_DSP_SETFMT");
230 msg_err("Cannot set audio format");
231 goto error;
232 }
233 if (arg != want_arg) {
234 LOG_ERRX("%d: audio format not supported", want_arg);
235 msg_errx("Audio format not supported");
236 goto error;
237 }
238
239 /* Set sampling rate. */
240 arg = sf->rate;
241 if (ioctl(op_oss_fd, SNDCTL_DSP_SPEED, &arg) == -1) {
242 LOG_ERR("ioctl: SNDCTL_DSP_SPEED");
243 msg_err("Cannot set sampling rate");
244 goto error;
245 }
246 /* Allow a 0.5% deviation in the sampling rate. */
247 if ((unsigned int)arg < sf->rate * 995 / 1000 ||
248 (unsigned int)arg > sf->rate * 1005 / 1000) {
249 LOG_ERRX("sampling rate (%u Hz) not supported", sf->rate);
250 msg_errx("Sampling rate not supported");
251 goto error;
252 }
253
254 /* Set byte order of sample format. */
255 #if AFMT_S16_NE == AFMT_S16_BE
256 sf->byte_order = BYTE_ORDER_BIG;
257 #else
258 sf->byte_order = BYTE_ORDER_LITTLE;
259 #endif
260
261 /*
262 * Determine the optimal buffer size. This is not relevant on OSS 4,
263 * but it is on older OSS versions.
264 */
265 if (ioctl(op_oss_fd, SNDCTL_DSP_GETBLKSIZE, &arg) == -1) {
266 LOG_ERR("ioctl: SNDCTL_DSP_GETBLKSIZE");
267 op_oss_buffer_size = OP_OSS_BUFSIZE;
268 } else
269 op_oss_buffer_size = arg;
270
271 return 0;
272
273 error:
274 close(op_oss_fd);
275 op_oss_fd = -1;
276 return -1;
277 }
278
279 static int
op_oss_stop(void)280 op_oss_stop(void)
281 {
282 #ifdef OP_OSS_HAVE_VOLUME_SUPPORT
283 int vol;
284
285 /* Save the current volume level before closing the device. */
286 if (op_oss_volume != -1) {
287 vol = op_oss_get_volume();
288 if (vol != -1)
289 op_oss_volume = vol;
290 }
291 #endif
292
293 close(op_oss_fd);
294 op_oss_fd = -1;
295 return 0;
296 }
297
298 static int
op_oss_write(struct sample_buffer * sb)299 op_oss_write(struct sample_buffer *sb)
300 {
301 if (write(op_oss_fd, sb->data, sb->len_b) == -1) {
302 LOG_ERR("write: %s", op_oss_device);
303 msg_err("Playback error");
304 return -1;
305 }
306 return 0;
307 }
308