1 /*
2  * Copyright (c) 2013 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 <errno.h>
18 #include <stdarg.h>
19 #include <string.h>
20 
21 #include <alsa/asoundlib.h>
22 
23 #include "../siren.h"
24 
25 #define OP_ALSA_PCM_DEVICE	"default"
26 #define OP_ALSA_MIXER_DEVICE	"default"
27 #define OP_ALSA_MIXER_ELEM	"PCM"
28 
29 static void		 op_alsa_close(void);
30 static size_t		 op_alsa_get_buffer_size(void);
31 static int		 op_alsa_get_volume(void);
32 static int		 op_alsa_get_volume_support(void);
33 static int		 op_alsa_init(void);
34 static int		 op_alsa_open(void);
35 static void		 op_alsa_set_volume(unsigned int);
36 static int		 op_alsa_start(struct sample_format *);
37 static int		 op_alsa_stop(void);
38 static int		 op_alsa_write(struct sample_buffer *);
39 
40 const struct op		 op = {
41 	"alsa",
42 	OP_PRIORITY_ALSA,
43 	NULL,
44 	op_alsa_close,
45 	op_alsa_get_buffer_size,
46 	op_alsa_get_volume,
47 	op_alsa_get_volume_support,
48 	op_alsa_init,
49 	op_alsa_open,
50 	op_alsa_set_volume,
51 	op_alsa_start,
52 	op_alsa_stop,
53 	op_alsa_write
54 };
55 
56 static snd_pcm_t	*op_alsa_pcm_handle;
57 static snd_mixer_t	*op_alsa_mixer_handle;
58 static snd_mixer_elem_t	*op_alsa_mixer_elem;
59 static char		*op_alsa_mixer_dev;
60 static size_t		 op_alsa_bufsize;
61 static size_t		 op_alsa_framesize;
62 
63 static void
64 op_alsa_close(void)
65 {
66 	snd_pcm_close(op_alsa_pcm_handle);
67 
68 	if (op_alsa_mixer_handle != NULL) {
69 		snd_mixer_free(op_alsa_mixer_handle);
70 		snd_mixer_detach(op_alsa_mixer_handle, op_alsa_mixer_dev);
71 		snd_mixer_close(op_alsa_mixer_handle);
72 		free(op_alsa_mixer_dev);
73 	}
74 }
75 
76 static size_t
77 op_alsa_get_buffer_size(void)
78 {
79 	return op_alsa_bufsize;
80 }
81 
82 static int
83 op_alsa_get_volume(void)
84 {
85 	long int	volume;
86 	int		ret;
87 
88 	if (op_alsa_mixer_handle == NULL)
89 		return -1;
90 
91 	ret = snd_mixer_handle_events(op_alsa_mixer_handle);
92 	if (ret < 0)
93 		LOG_ERRX("snd_mixer_handle_events: %s", snd_strerror(ret));
94 
95 	/*
96 	 * SND_MIXER_SCHN_MONO is an alias for SND_MIXER_SCHN_FRONT_LEFT. We
97 	 * assume all channels have the same value.
98 	 */
99 	ret = snd_mixer_selem_get_playback_volume(op_alsa_mixer_elem,
100 	    SND_MIXER_SCHN_MONO, &volume);
101 	if (ret) {
102 		LOG_ERRX("snd_mixer_get_playback_volume: %s",
103 		    snd_strerror(ret));
104 		msg_errx("Cannot get volume: %s", snd_strerror(ret));
105 		return -1;
106 	}
107 
108 	return volume;
109 }
110 
111 static int
112 op_alsa_get_volume_support(void)
113 {
114 	return op_alsa_mixer_handle != NULL;
115 }
116 
117 PRINTFLIKE(5, 0) static void
118 op_alsa_handle_error(const char *file, int line, const char *func, int errnum,
119     const char *fmt, ...)
120 {
121 	va_list	 ap;
122 	char	*msg;
123 
124 	va_start(ap, fmt);
125 	xvasprintf(&msg, fmt, ap);
126 	va_end(ap);
127 
128 	if (errnum == 0)
129 		LOG_ERRX("%s:%d: %s: %s", file, line, func, msg);
130 	else {
131 		errno = errnum;
132 		LOG_ERR("%s:%d: %s: %s", file, line, func, msg);
133 	}
134 
135 	free(msg);
136 }
137 
138 static int
139 op_alsa_init(void)
140 {
141 	option_add_string("alsa-mixer-device", OP_ALSA_MIXER_DEVICE,
142 	    player_reopen_op);
143 	option_add_string("alsa-mixer-element", OP_ALSA_MIXER_ELEM,
144 	    player_reopen_op);
145 	option_add_string("alsa-pcm-device", OP_ALSA_PCM_DEVICE,
146 	    player_reopen_op);
147 	snd_lib_error_set_handler(op_alsa_handle_error);
148 	return 0;
149 }
150 
151 static int
152 op_alsa_open(void)
153 {
154 	int	 ret;
155 	char	*dev, *elem;
156 
157 	/*
158 	 * Open the PCM device.
159 	 */
160 
161 	dev = option_get_string("alsa-pcm-device");
162 
163 	ret = snd_pcm_open(&op_alsa_pcm_handle, dev, SND_PCM_STREAM_PLAYBACK,
164 	    0);
165 	if (ret) {
166 		LOG_ERRX("snd_pcm_open: %s: %s", dev, snd_strerror(ret));
167 		msg_errx("Cannot open device %s: %s", dev, snd_strerror(ret));
168 		free(dev);
169 		return -1;
170 	}
171 
172 	LOG_INFO("using %s PCM device", dev);
173 	free(dev);
174 
175 	/*
176 	 * Open the mixer device.
177 	 */
178 
179 	op_alsa_mixer_handle = NULL;
180 
181 	/* Open an empty mixer. */
182 	ret = snd_mixer_open(&op_alsa_mixer_handle, 0);
183 	if (ret) {
184 		LOG_ERRX("snd_mixer_open: %s", snd_strerror(ret));
185 		msg_errx("Cannot open mixer: %s", snd_strerror(ret));
186 		return 0;
187 	}
188 
189 	op_alsa_mixer_dev = option_get_string("alsa-mixer-device");
190 
191 	/* Attach to the mixer device. */
192 	ret = snd_mixer_attach(op_alsa_mixer_handle, op_alsa_mixer_dev);
193 	if (ret) {
194 		LOG_ERRX("snd_mixer_attach: %s: %s", op_alsa_mixer_dev,
195 		    snd_strerror(ret));
196 		msg_errx("Cannot attach to mixer device %s: %s",
197 		    op_alsa_mixer_dev, snd_strerror(ret));
198 		goto error1;
199 	}
200 
201 	LOG_INFO("using %s mixer device", op_alsa_mixer_dev);
202 
203 	/* Register mixer elements. */
204 	ret = snd_mixer_selem_register(op_alsa_mixer_handle, NULL, NULL);
205 	if (ret) {
206 		LOG_ERRX("snd_mixer_selem_register: %s", snd_strerror(ret));
207 		goto error2;
208 	}
209 
210 	/* Load mixer elements. */
211 	ret = snd_mixer_load(op_alsa_mixer_handle);
212 	if (ret) {
213 		LOG_ERRX("snd_mixer_load: %s", snd_strerror(ret));
214 		goto error2;
215 	}
216 
217 	elem = option_get_string("alsa-mixer-element");
218 
219 	/* Search for the specified mixer element. */
220 	op_alsa_mixer_elem = snd_mixer_first_elem(op_alsa_mixer_handle);
221 	while (op_alsa_mixer_elem != NULL) {
222 		if (!strcmp(elem,
223 		    snd_mixer_selem_get_name(op_alsa_mixer_elem)))
224 			break;
225 		op_alsa_mixer_elem = snd_mixer_elem_next(op_alsa_mixer_elem);
226 	}
227 
228 	if (op_alsa_mixer_elem == NULL) {
229 		LOG_ERRX("%s: mixer element not found", elem);
230 		msg_errx("Mixer element not found: %s", elem);
231 		free(elem);
232 		goto error3;
233 	}
234 
235 	LOG_INFO("using %s mixer element", elem);
236 	free(elem);
237 
238 	/* Check if the mixer element has a playback-volume control. */
239 	if (!snd_mixer_selem_has_playback_volume(op_alsa_mixer_elem)) {
240 		LOG_ERRX("mixer element does not have playback volume");
241 		goto error3;
242 	}
243 
244 	/* Set the volume range to 0-100. */
245 	ret = snd_mixer_selem_set_playback_volume_range(op_alsa_mixer_elem, 0,
246 	    100);
247 	if (ret) {
248 		LOG_ERRX("snd_mixer_selem_set_playback_volume_range: %s",
249 		    snd_strerror(ret));
250 		goto error3;
251 	}
252 
253 	return 0;
254 
255 error3:
256 	snd_mixer_free(op_alsa_mixer_handle);
257 
258 error2:
259 	snd_mixer_detach(op_alsa_mixer_handle, op_alsa_mixer_dev);
260 
261 error1:
262 	snd_mixer_close(op_alsa_mixer_handle);
263 	op_alsa_mixer_handle = NULL;
264 	free(op_alsa_mixer_dev);
265 
266 	return 0;
267 }
268 
269 static void
270 op_alsa_set_volume(unsigned int volume)
271 {
272 	int ret;
273 
274 	if (op_alsa_mixer_handle == NULL)
275 		return;
276 
277 	ret = snd_mixer_selem_set_playback_volume_all(op_alsa_mixer_elem,
278 	    volume);
279 	if (ret) {
280 		LOG_ERRX("snd_mixer_selem_set_playback_volume_all: %s",
281 		    snd_strerror(ret));
282 		msg_errx("Cannot set volume: %s", snd_strerror(ret));
283 	}
284 }
285 
286 static int
287 op_alsa_start(struct sample_format *sf)
288 {
289 	snd_pcm_hw_params_t	*params;
290 	snd_pcm_format_t	 format;
291 	snd_pcm_uframes_t	 nframes;
292 	int			 dir, ret;
293 	unsigned int		 rate;
294 
295 	/* Allocate memory. */
296 	ret = snd_pcm_hw_params_malloc(&params);
297 	if (ret) {
298 		LOG_ERRX("snd_pcm_hw_malloc: %s", snd_strerror(ret));
299 		goto error;
300 	}
301 
302 	/* Set defaults. */
303 	snd_pcm_hw_params_any(op_alsa_pcm_handle, params);
304 
305 	/* Set access type. */
306 	ret = snd_pcm_hw_params_set_access(op_alsa_pcm_handle, params,
307 	    SND_PCM_ACCESS_RW_INTERLEAVED);
308 	if (ret) {
309 		LOG_ERRX("snd_pcm_hw_params_set_access: %s",
310 		    snd_strerror(ret));
311 		goto error;
312 	}
313 
314 	/* Determine format. */
315 	if (sf->nbits <= 8)
316 		format = SND_PCM_FORMAT_S8;
317 	else if (sf->nbits <= 16)
318 		format = SND_PCM_FORMAT_S16;
319 	else if (sf->nbits <= 24)
320 		format = SND_PCM_FORMAT_S24;
321 	else
322 		format = SND_PCM_FORMAT_S32;
323 
324 	/* Set format. */
325 	ret = snd_pcm_hw_params_set_format(op_alsa_pcm_handle, params, format);
326 	if (ret) {
327 		LOG_ERRX("snd_pcm_hw_params_set: %s", snd_strerror(ret));
328 		goto error;
329 	}
330 
331 	/* Set number of channels. */
332 	ret = snd_pcm_hw_params_set_channels(op_alsa_pcm_handle, params,
333 	    sf->nchannels);
334 	if (ret) {
335 		LOG_ERRX("snd_pcm_hw_params_set_channels: %s",
336 		    snd_strerror(ret));
337 		goto error;
338 	}
339 
340 	/* Set sampling rate. */
341 	dir = 0;
342 	rate = sf->rate;
343 	ret = snd_pcm_hw_params_set_rate_near(op_alsa_pcm_handle, params,
344 	    &rate, &dir);
345 	if (ret) {
346 		LOG_ERRX("snd_pcm_hw_params_set_rate_near: %s",
347 		    snd_strerror(ret));
348 		goto error;
349 	}
350 
351 	/* Configure the device. */
352 	ret = snd_pcm_hw_params(op_alsa_pcm_handle, params);
353 	if (ret) {
354 		LOG_ERRX("snd_pcm_hw_params: %s", snd_strerror(ret));
355 		goto error;
356 	}
357 
358 	/*
359 	 * The ALSA application buffer is divided into periods. Determine the
360 	 * size of 1 period and use that as the size of our buffer.
361 	 */
362 	snd_pcm_hw_params_get_period_size(params, &nframes, &dir);
363 	op_alsa_framesize = ((sf->nbits + 7) / 8) * sf->nchannels;
364 	op_alsa_bufsize = nframes * op_alsa_framesize;
365 
366 	snd_pcm_hw_params_free(params);
367 
368 	sf->byte_order = player_get_byte_order();
369 
370 	LOG_INFO("format=%s, channels=%u, rate=%u, bufsize=%zu",
371 	    snd_pcm_format_name(format), sf->nchannels, rate, op_alsa_bufsize);
372 	return 0;
373 
374 error:
375 	snd_pcm_hw_params_free(params);
376 	msg_errx("Cannot start playback: %s", snd_strerror(ret));
377 	return -1;
378 }
379 
380 static int
381 op_alsa_stop(void)
382 {
383 	snd_pcm_drain(op_alsa_pcm_handle);
384 	return 0;
385 }
386 
387 static int
388 op_alsa_write(struct sample_buffer *sb)
389 {
390 	snd_pcm_sframes_t ret;
391 
392 	ret = snd_pcm_writei(op_alsa_pcm_handle, sb->data,
393 	    sb->len_b / op_alsa_framesize);
394 	if (ret == -EPIPE) {
395 		/* An underrun occurred; attempt to recover. */
396 		LOG_ERRX("snd_pcm_writei: %s", snd_strerror(ret));
397 		ret = snd_pcm_prepare(op_alsa_pcm_handle);
398 		if (ret) {
399 			LOG_ERRX("snd_pcm_prepare: %s", snd_strerror(ret));
400 			msg_errx("Playback error: %s", snd_strerror(ret));
401 			return -1;
402 		}
403 	} else if (ret < 0) {
404 		LOG_ERRX("snd_pcm_writei: %s", snd_strerror(ret));
405 		msg_errx("Playback error: %s", snd_strerror(ret));
406 		return -1;
407 	}
408 	return 0;
409 }
410