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