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