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(¶ms); 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