1 /*
2 * ALSA Sound Driver for xMAME
3 *
4 * Copyright 2000 Luc Saillard <luc.saillard@alcove.fr>
5 * Copyright 2001, 2002, 2003 Shyouzou Sugitani <shy@debian.or.jp>
6 *
7 * This file and the acompanying files in this directory are free software;
8 * you can redistribute them and/or modify them under the terms of the GNU
9 * Library General Public License as published by the Free Software Foundation;
10 * either version 2 of the License, or (at your option) any later version.
11 *
12 * These files are distributed in the hope that they will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with these files; see the file COPYING.LIB. If not,
19 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 * Boston, MA 02111-1307, USA.
21 *
22 * Changelog:
23 * v 0.1 Thu, 10 Aug 2000 08:29:00 +0200
24 * - initial release
25 * - TODO: find the best sound card to play sound.
26 * v 0.2 Wed, 13 Sep 2000 Shyouzou Sugitani <shy@debian.or.jp>
27 * - change from block to stream mode.
28 * v 0.3 Sat, 16 Sep 2000 Shyouzou Sugitani <shy@debian.or.jp>
29 * - one important bug fix, performance improvements and code cleanup.
30 * v 0.4 Sun, 15 Apr 2001 Shyouzou Sugitani <shy@debian.or.jp>
31 * - minor cosmetic changes.
32 * - suppression of bogus warnings about underruns.
33 * - TODO: add support for ALSA 0.9 API.
34 * v 0.5 Thu, 17 May 2001 Shyouzou Sugitani <shy@debian.or.jp>
35 * - added preliminary support for ALSA 0.9 API.
36 * - split of the 0.5 and 0.9 API stuff into separate files.
37 * v 0.6 Sat, 19 May 2001 Shyouzou Sugitani <shy@debian.or.jp>
38 * - update of the 0.9 API stuff.
39 * added -list-alsa-pcm option.
40 * improved write error handling.
41 * v 0.7 Sat, 08 Sep 2001 Shyouzou Sugitani <shy@debian.or.jp>
42 * - update of the 0.9 API stuff.
43 * added -alsa-buffer option.
44 * use SND_PCM_FORMAT_S16 instead of SND_PCM_FORMAT_S16_{LE,BE}.
45 * v 0.8 Thu, 13 Sep 2001 Shyouzou Sugitani <shy@debian.or.jp>
46 * - update of the 0.9 API stuff.
47 * changed the -alsapcm(-pcm) to -alsa-pcm(-apcm).
48 * changed the default value of the -alsa-pcm.
49 * V 0.8a Stephen Anthony
50 * - Fixed a problem in the ALSA 0.9 driver with setting the sample rate
51 * on SB128 soundcards.
52 * V 0.9 Mon, 13 Jan 2003 Shyouzou Sugitani <shy@debian.or.jp>
53 * - removed the 0.5 API support.
54 *
55 */
56
57 #include "xmame.h" /* xMAME common header */
58 #include "devices.h" /* xMAME device header */
59
60 #ifdef SYSDEP_DSP_ALSA
61
62 #include <sys/ioctl.h> /* System and I/O control */
63 #include <alsa/asoundlib.h> /* ALSA sound library header */
64 #include "sysdep/sysdep_dsp.h"
65 #include "sysdep/sysdep_dsp_priv.h"
66 #include "sysdep/plugin_manager.h"
67
68 /* our per instance private data struct */
69 struct alsa_dsp_priv_data
70 {
71 snd_pcm_t *pcm_handle;
72 };
73
74 /* public methods prototypes (static but exported through the sysdep_dsp or
75 plugin struct) */
76 static int alsa_dsp_init(void);
77 static void *alsa_dsp_create(const void *flags);
78 static void alsa_dsp_destroy(struct sysdep_dsp_struct *dsp);
79 static int alsa_dsp_get_freespace(struct sysdep_dsp_struct *dsp);
80 static int alsa_dsp_write(struct sysdep_dsp_struct *dsp, unsigned char *data,
81 int count);
82 static int alsa_device_list(struct rc_option *option, const char *arg,
83 int priority);
84 static int alsa_pcm_list(struct rc_option *option, const char *arg,
85 int priority);
86 static int alsa_dsp_set_params(struct alsa_dsp_priv_data *priv);
87
88 /* public variables */
89
90 static struct {
91 snd_pcm_format_t format;
92 unsigned int channels;
93 unsigned int rate;
94 } pcm_params;
95
96 static char *pcm_name = NULL;
97 static snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
98 static size_t bits_per_sample, bits_per_frame;
99 static unsigned int buffer_time;
100
101 struct rc_option alsa_dsp_opts[] = {
102 /* name, shortname, type, dest, deflt, min, max, func, help */
103 { "Alsa Sound System", NULL, rc_seperator, NULL,
104 NULL, 0, 0, NULL,
105 NULL },
106 { "list-alsa-cards", NULL, rc_use_function_no_arg, NULL,
107 NULL, 0, 0, alsa_device_list,
108 "List available sound cards" },
109 { "list-alsa-pcm", NULL, rc_use_function_no_arg, NULL,
110 NULL, 0, 0, alsa_pcm_list,
111 "List available pcm devices" },
112 { "alsa-pcm", "apcm", rc_string, &pcm_name,
113 "default", 0, 0, NULL,
114 "Specify the PCM by name" },
115 { "alsa-buffer", "abuf", rc_int, &buffer_time,
116 "250000", 0, 0, NULL,
117 "Set the buffer size [micro sec] (default: 250000)" },
118 { NULL, NULL, rc_end, NULL,
119 NULL, 0, 0, NULL,
120 NULL }
121 };
122
123 const struct plugin_struct sysdep_dsp_alsa = {
124 "alsa",
125 "sysdep_dsp",
126 "Alsa Sound System DSP plugin",
127 alsa_dsp_opts,
128 alsa_dsp_init,
129 NULL, /* no exit */
130 alsa_dsp_create,
131 4 /* high priority */
132 };
133
134 /* private variables */
135 static int alsa_dsp_bytes_per_sample[4] = SYSDEP_DSP_BYTES_PER_SAMPLE;
136
137
138 /* public methods (static but exported through the sysdep_dsp or plugin
139 struct) */
140
141 /*
142 * Function name : alsa_dsp_init
143 *
144 * Description : Detect if a card is present on the machine
145 * Output :
146 * a boolean
147 */
alsa_dsp_init(void)148 static int alsa_dsp_init(void)
149 {
150 int card = -1;
151
152 if (snd_card_next(&card) < 0 || card < 0) {
153 fprintf(stderr, "No cards detected.\n"
154 "ALSA sound disabled.\n");
155 return 1;
156 }
157 return 0;
158 }
159
160 /*
161 * Function name : alsa_dsp_create
162 *
163 * Description : Create an instance of dsp plugins
164 * Input :
165 * flags: a ptr to struct sysdep_dsp_create_params
166 * Output :
167 * a ptr to a struct sysdep_dsp_struct
168 */
alsa_dsp_create(const void * flags)169 static void *alsa_dsp_create(const void *flags)
170 {
171 int err;
172 struct alsa_dsp_priv_data *priv = NULL;
173 struct sysdep_dsp_struct *dsp = NULL;
174 const struct sysdep_dsp_create_params *params = flags;
175 snd_pcm_info_t *info;
176 int open_mode = 0;
177
178 /* allocate the dsp struct */
179 dsp = calloc(1, sizeof(struct sysdep_dsp_struct));
180 if (!dsp) {
181 fprintf(stderr,
182 "error malloc failed for struct sysdep_dsp_struct\n");
183 return NULL;
184 }
185
186 /* alloc private data */
187 priv = calloc(1, sizeof(struct alsa_dsp_priv_data));
188 if(!priv) {
189 fprintf(stderr,
190 "error malloc failed for struct alsa_dsp_priv_data\n");
191 alsa_dsp_destroy(dsp);
192 return NULL;
193 }
194
195 /* fill in the functions and some data */
196 memset(priv,0,sizeof(struct alsa_dsp_priv_data));
197 dsp->_priv = priv;
198 dsp->get_freespace = alsa_dsp_get_freespace;
199 dsp->write = alsa_dsp_write;
200 dsp->destroy = alsa_dsp_destroy;
201 dsp->hw_info.type = params->type;
202 dsp->hw_info.samplerate = params->samplerate;
203 dsp->hw_info.bufsize = 0;
204
205 open_mode |= SND_PCM_NONBLOCK;
206
207 pcm_params.format = (dsp->hw_info.type & SYSDEP_DSP_16BIT) ?
208 SND_PCM_FORMAT_S16 /* Signed 16 bit CPU endian */ :
209 SND_PCM_FORMAT_U8;
210
211 /* rate >= 2000 && rate <= 128000 */
212 pcm_params.rate = dsp->hw_info.samplerate;
213 pcm_params.channels = (dsp->hw_info.type & SYSDEP_DSP_STEREO) ? 2 : 1;
214
215 err = snd_pcm_open(&priv->pcm_handle, pcm_name, stream, open_mode);
216 if (err < 0) {
217 fprintf(stderr_file, "Alsa error: audio open error: %s\n",
218 snd_strerror(err));
219 return NULL;
220 }
221
222 snd_pcm_info_alloca(&info);
223 err = snd_pcm_info(priv->pcm_handle, info);
224 if (err < 0) {
225 fprintf(stderr_file, "Alsa error: info error: %s\n",
226 snd_strerror(err));
227 return NULL;
228 }
229 /* set non-blocking mode if selected */
230 if (params->flags & SYSDEP_DSP_O_NONBLOCK) {
231 err = snd_pcm_nonblock(priv->pcm_handle, 1);
232 if (err < 0) {
233 fprintf(stderr_file,
234 "Alsa error: nonblock setting error: %s\n",
235 snd_strerror(err));
236 return NULL;
237 }
238 }
239
240 fprintf(stderr_file, "info: set to %dbit linear %s %dHz\n",
241 (dsp->hw_info.type & SYSDEP_DSP_16BIT) ? 16 : 8,
242 (dsp->hw_info.type & SYSDEP_DSP_STEREO) ? "stereo" : "mono",
243 dsp->hw_info.samplerate);
244
245 if (alsa_dsp_set_params(priv) == 0)
246 return NULL;
247
248 return dsp;
249 }
250
251 /*
252 * Function name : alsa_dsp_destroy
253 *
254 * Description :
255 * Input :
256 * Output :
257 */
alsa_dsp_destroy(struct sysdep_dsp_struct * dsp)258 static void alsa_dsp_destroy(struct sysdep_dsp_struct *dsp)
259 {
260 struct alsa_dsp_priv_data *priv = dsp->_priv;
261
262 if (priv) {
263 if (priv->pcm_handle) {
264 snd_pcm_close(priv->pcm_handle);
265 }
266 free(priv);
267 }
268 free(dsp);
269 }
270
271 /*
272 * Function name : alsa_dsp_get_freespace
273 *
274 * Description :
275 * Input :
276 * Output :
277 */
alsa_dsp_get_freespace(struct sysdep_dsp_struct * dsp)278 static int alsa_dsp_get_freespace(struct sysdep_dsp_struct *dsp)
279 {
280 int err;
281 struct alsa_dsp_priv_data *priv = dsp->_priv;
282 snd_pcm_status_t *status;
283 snd_pcm_uframes_t frames;
284
285 snd_pcm_status_alloca(&status);
286 err = snd_pcm_status(priv->pcm_handle, status);
287 if (err < 0) {
288 fprintf(stderr_file, "Alsa error: status error: %s\n",
289 snd_strerror(err));
290 return -1;
291 }
292 frames = snd_pcm_status_get_avail(status);
293 if (frames < 0)
294 return -1;
295 else
296 return frames * bits_per_frame / 8
297 / alsa_dsp_bytes_per_sample[dsp->hw_info.type];
298 }
299
300 /*
301 * Function name : alsa_dsp_write
302 *
303 * Description :
304 * Input :
305 * Output :
306 */
alsa_dsp_write(struct sysdep_dsp_struct * dsp,unsigned char * data,int count)307 static int alsa_dsp_write(struct sysdep_dsp_struct *dsp, unsigned char *data,
308 int count)
309 {
310 int data_size, result;
311 struct alsa_dsp_priv_data *priv = dsp->_priv;
312
313 data_size = count * alsa_dsp_bytes_per_sample[dsp->hw_info.type]
314 * 8 / bits_per_frame;
315
316 result = snd_pcm_writei(priv->pcm_handle, data, data_size);
317 if (result == -EAGAIN) {
318 return 0;
319 } else if (result == -EPIPE) {
320 int err;
321 snd_pcm_status_t *status;
322
323 snd_pcm_status_alloca(&status);
324 err = snd_pcm_status(priv->pcm_handle, status);
325 if (err < 0) {
326 fprintf(stderr_file,
327 "Alsa error: status error: %s\n",
328 snd_strerror(err));
329 return -1;
330 }
331 if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) {
332 err = snd_pcm_prepare(priv->pcm_handle);
333 if (err < 0) {
334 fprintf(stderr_file,
335 "Alsa error: prepare error: %s\n",
336 snd_strerror(err));
337 return -1;
338 }
339 /* ok, data should be accepted again */
340 return 0;
341 }
342 fprintf(stderr_file, "Alsa error: write error: %s\n",
343 snd_strerror(result));
344 return -1;
345 } else if (result < 0) {
346 fprintf(stderr_file, "Alsa error: write error: %s\n",
347 snd_strerror(result));
348 return -1;
349 }
350
351 return result * bits_per_frame / 8
352 / alsa_dsp_bytes_per_sample[dsp->hw_info.type];
353 }
354
355 /*
356 * Function name : alsa_device_list
357 *
358 * Description :
359 * Input :
360 * Output :
361 */
alsa_device_list(struct rc_option * option,const char * arg,int priority)362 static int alsa_device_list(struct rc_option *option, const char *arg,
363 int priority)
364 {
365 snd_ctl_t *handle;
366 int card, err, dev;
367 snd_ctl_card_info_t *info;
368 snd_pcm_info_t *pcminfo;
369 snd_ctl_card_info_alloca(&info);
370 snd_pcm_info_alloca(&pcminfo);
371
372 card = -1;
373 if (snd_card_next(&card) < 0 || card < 0) {
374 printf("Alsa: no soundcards found...\n");
375 return -1;
376 }
377 fprintf(stdout, "Alsa cards:\n");
378 while (card >= 0) {
379 char name[32];
380 sprintf(name, "hw:%d", card);
381 err = snd_ctl_open(&handle, name, 0);
382 if (err < 0) {
383 fprintf(stderr, "Alsa error: control open (%i): %s\n",
384 card, snd_strerror(err));
385 continue;
386 }
387 err = snd_ctl_card_info(handle, info);
388 if (err < 0) {
389 fprintf(stderr,
390 "Alsa error: control hardware info (%i): %s\n",
391 card, snd_strerror(err));
392 snd_ctl_close(handle);
393 continue;
394 }
395 dev = -1;
396 while (1) {
397 int idx;
398 unsigned int count;
399
400 if (snd_ctl_pcm_next_device(handle, &dev) < 0)
401 ;
402 if (dev < 0)
403 break;
404 snd_pcm_info_set_device(pcminfo, dev);
405 snd_pcm_info_set_subdevice(pcminfo, 0);
406 snd_pcm_info_set_stream(pcminfo, stream);
407 err = snd_ctl_pcm_info(handle, pcminfo);
408 if (err < 0) {
409 if (err != -ENOENT)
410 fprintf(stderr,
411 "Alsa error: control digital audio info (%i): %s\n",
412 card, snd_strerror(err));
413 continue;
414 }
415 fprintf(stderr,
416 "card %i: %s [%s], device %i: %s [%s]\n",
417 card,
418 snd_ctl_card_info_get_id(info),
419 snd_ctl_card_info_get_name(info),
420 dev,
421 snd_pcm_info_get_id(pcminfo),
422 snd_pcm_info_get_name(pcminfo));
423 count = snd_pcm_info_get_subdevices_count(pcminfo);
424 fprintf(stderr, " Subdevices: %i/%i\n",
425 snd_pcm_info_get_subdevices_avail(pcminfo),
426 count);
427 for (idx = 0; idx < count; idx++) {
428 snd_pcm_info_set_subdevice(pcminfo, idx);
429 err = snd_ctl_pcm_info(handle, pcminfo);
430 if (err < 0) {
431 fprintf(stderr,
432 "Alsa error: control digital audio playback info (%i): %s",
433 card, snd_strerror(err));
434 } else {
435 fprintf(stderr,
436 " Subdevice #%i: %s\n",
437 idx, snd_pcm_info_get_subdevice_name(pcminfo));
438 }
439 }
440 }
441 snd_ctl_close(handle);
442 if (snd_card_next(&card) < 0) {
443 break;
444 }
445 }
446 return -1;
447 }
448
449 /*
450 * Function name : alsa_pcm_list
451 *
452 * Description :
453 * Input :
454 * Output :
455 */
alsa_pcm_list(struct rc_option * option,const char * arg,int priority)456 static int alsa_pcm_list(struct rc_option *option, const char *arg,
457 int priority)
458 {
459 snd_config_t *conf;
460 snd_output_t *out;
461
462 int err;
463
464 err = snd_config_update();
465 if (err < 0) {
466 fprintf(stderr, "Alsa error: snd_config_update: %s\n",
467 snd_strerror(err));
468 return -1;
469 }
470 snd_output_stdio_attach(&out, stderr, 0);
471 err = snd_config_search(snd_config, "pcm", &conf);
472 if (err < 0)
473 return -1;
474 fprintf(stderr, "ALSA PCM devices:\n");
475 snd_config_save(conf, out);
476 snd_output_close(out);
477
478 return -1;
479 }
480
481 /*
482 * Function name : alsa_dsp_set_params
483 *
484 * Description :
485 * Input :
486 * priv: a ptr to struct alsa_dsp_priv_data
487 * Output :
488 * priv is modified with the current parameters.
489 * a boolean if the card accept the value.
490 *
491 */
alsa_dsp_set_params(struct alsa_dsp_priv_data * priv)492 static int alsa_dsp_set_params(struct alsa_dsp_priv_data *priv)
493 {
494 snd_pcm_hw_params_t *hw_params;
495 snd_pcm_sw_params_t *sw_params;
496
497 size_t buffer_size;
498 int chunk_size;
499 int err;
500
501 snd_pcm_hw_params_alloca(&hw_params);
502 snd_pcm_sw_params_alloca(&sw_params);
503 err = snd_pcm_hw_params_any(priv->pcm_handle, hw_params);
504 if (err < 0) {
505 fprintf(stderr_file,
506 "Alsa error: no configurations available\n");
507 return 0;
508 }
509
510 if (snd_pcm_hw_params_set_access(priv->pcm_handle, hw_params,
511 SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
512 fprintf(stderr_file,
513 "Alsa error: interleaved access mode non available\n");
514 return 0;
515 }
516
517 err = snd_pcm_hw_params_set_format(priv->pcm_handle, hw_params, pcm_params.format);
518 if (err < 0) {
519 fprintf(stderr_file,
520 "Alsa error: requested format %s isn't supported with hardware\n",
521 snd_pcm_format_name(pcm_params.format));
522 return 0;
523 }
524
525 err = snd_pcm_hw_params_set_channels(priv->pcm_handle, hw_params, pcm_params.channels);
526 if (err < 0) {
527 fprintf(stderr_file,
528 "Alsa error: channels count non available\n");
529 return 0;
530 }
531
532 if (snd_pcm_hw_params_set_rate_near(priv->pcm_handle, hw_params, pcm_params.rate, 0) < 0) {
533 fprintf(stderr_file,
534 "Alsa error: unsupported rate %iHz (valid range is %iHz-%iHz)\n",
535 pcm_params.rate,
536 snd_pcm_hw_params_get_rate_min(hw_params, 0),
537 snd_pcm_hw_params_get_rate_max(hw_params, 0));
538 return 0;
539 }
540
541 snd_pcm_hw_params_set_buffer_time_near(priv->pcm_handle, hw_params, buffer_time, 0);
542 snd_pcm_hw_params_set_period_size_near(priv->pcm_handle, hw_params, 1, 0);
543
544 err = snd_pcm_hw_params(priv->pcm_handle, hw_params);
545 if (err < 0) {
546 fprintf(stderr_file,
547 "Alsa error: Unable to install hw params\n");
548 return 0;
549 }
550
551 chunk_size = snd_pcm_hw_params_get_period_size(hw_params, 0);
552 buffer_size = snd_pcm_hw_params_get_buffer_size(hw_params);
553 if (chunk_size == buffer_size) {
554 fprintf(stderr_file,
555 "Alsa error: cannot use period equal to buffer size (%u == %lu)\n",
556 chunk_size, (long)buffer_size);
557 return 0;
558 }
559
560 snd_pcm_sw_params_current(priv->pcm_handle, sw_params);
561
562 snd_pcm_sw_params_set_sleep_min(priv->pcm_handle, sw_params, 0);
563 snd_pcm_sw_params_set_xfer_align(priv->pcm_handle, sw_params, 1);
564 snd_pcm_sw_params_set_avail_min(priv->pcm_handle, sw_params, 1);
565
566 snd_pcm_sw_params_set_start_threshold(priv->pcm_handle, sw_params, 1);
567
568 snd_pcm_sw_params_set_stop_threshold(priv->pcm_handle, sw_params, buffer_size);
569
570 if (snd_pcm_sw_params(priv->pcm_handle, sw_params) < 0) {
571 fprintf(stderr_file, "Alsa error: unable to install sw params\n");
572 return 0;
573 }
574
575 #if 0 /* DEBUG */
576 {
577 snd_output_t *log;
578 snd_output_stdio_attach(&log, stderr_file, 0);
579 snd_pcm_dump(priv->pcm_handle, log);
580 snd_output_close(log);
581 }
582 #endif
583
584 bits_per_sample = snd_pcm_format_physical_width(pcm_params.format);
585 bits_per_frame = bits_per_sample * pcm_params.channels;
586
587 err = snd_pcm_prepare(priv->pcm_handle);
588 if (err < 0) {
589 fprintf(stderr_file,
590 "Alsa error: unable to prepare audio: %s\n",
591 snd_strerror(err));
592 return 0;
593 }
594
595 return 1;
596 }
597
598 #endif /* SYSDEP_DSP_ALSA */
599