1 /*
2  * gbsplay is a Gameboy sound player
3  *
4  * 2006 (C) by Tobias Diedrich <ranma+gbsplay@tdiedrich.de>
5  *
6  * Licensed under GNU GPL v1 or, at your option, any later version.
7  */
8 
9 #include "common.h"
10 
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <fcntl.h>
15 #include <errno.h>
16 #include <string.h>
17 #include <alsa/asoundlib.h>
18 
19 #include "plugout.h"
20 
21 /* Handle for the PCM device */
22 snd_pcm_t *pcm_handle;
23 
24 #if BYTE_ORDER == LITTLE_ENDIAN
25 #define SND_PCM_FORMAT_S16_NE SND_PCM_FORMAT_S16_LE
26 #else
27 #define SND_PCM_FORMAT_S16_NE SND_PCM_FORMAT_S16_BE
28 #endif
29 
alsa_open(enum plugout_endian endian,long rate)30 static long regparm alsa_open(enum plugout_endian endian, long rate)
31 {
32 	const char *pcm_name = "default";
33 	int fmt, err;
34 	unsigned exact_rate;
35 	snd_pcm_hw_params_t *hwparams;
36 
37 	switch (endian) {
38 	case PLUGOUT_ENDIAN_BIG: fmt = SND_PCM_FORMAT_S16_BE; break;
39 	case PLUGOUT_ENDIAN_LITTLE: fmt = SND_PCM_FORMAT_S16_LE; break;
40 	default:
41 	case PLUGOUT_ENDIAN_NATIVE: fmt = SND_PCM_FORMAT_S16_NE; break;
42 	}
43 
44 	snd_pcm_hw_params_alloca(&hwparams);
45 
46 	if ((err = snd_pcm_open(&pcm_handle, pcm_name, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
47 		fprintf(stderr, _("Could not open ALSA PCM device '%s': %s\n"), pcm_name, snd_strerror(err));
48 		return -1;
49 	}
50 
51 	if ((err = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) {
52 		fprintf(stderr, _("snd_pcm_hw_params_any failed: %s\n"), snd_strerror(err));
53 		return -1;
54 	}
55 
56 	if ((err = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
57 		fprintf(stderr, _("snd_pcm_hw_params_set_access failed: %s\n"), snd_strerror(err));
58 		return -1;
59 	}
60 
61 	if ((err = snd_pcm_hw_params_set_format(pcm_handle, hwparams, fmt)) < 0) {
62 		fprintf(stderr, _("snd_pcm_hw_params_set_format failed: %s\n"), snd_strerror(err));
63 		return -1;
64 	}
65 
66 	exact_rate = rate;
67 	if ((err = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0)) < 0) {
68 		fprintf(stderr, _("snd_pcm_hw_params_set_rate_near failed: %s\n"), snd_strerror(err));
69 		return -1;
70 	}
71 	if (rate != exact_rate) {
72 		fprintf(stderr, _("Requested rate %ldHz, got %dHz.\n"),
73 		        rate, exact_rate);
74 	}
75 
76 	if ((err = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, 2)) < 0) {
77 		fprintf(stderr, _("snd_pcm_hw_params_set_channels failed: %s\n"), snd_strerror(err));
78 		return -1;
79 	}
80 
81 	if ((err = snd_pcm_hw_params_set_periods(pcm_handle, hwparams, 4, 0)) < 0) {
82 		fprintf(stderr, _("snd_pcm_hw_params_set_periods failed: %s\n"), snd_strerror(err));
83 	}
84 
85 	if ((err = snd_pcm_hw_params_set_buffer_size(pcm_handle, hwparams, 8192)) < 0) {
86 		fprintf(stderr, _("snd_pcm_hw_params_set_buffer_size failed: %s\n"), snd_strerror(err));
87 	}
88 
89 	if ((err = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) {
90 		fprintf(stderr, _("snd_pcm_hw_params failed: %s\n"), snd_strerror(err));
91 		return -1;
92 	}
93 
94 	return 0;
95 }
96 
is_suspended(snd_pcm_sframes_t retval)97 static long is_suspended(snd_pcm_sframes_t retval)
98 {
99 #ifdef HAVE_ESTRPIPE
100 	return retval == -ESTRPIPE;
101 #else
102 	return snd_pcm_state(pcm_handle) == SND_PCM_STATE_SUSPENDED;
103 #endif
104 }
105 
alsa_write(const void * buf,size_t count)106 static ssize_t regparm alsa_write(const void *buf, size_t count)
107 {
108 	snd_pcm_sframes_t retval;
109 
110 	do {
111 		retval = snd_pcm_writei(pcm_handle, buf, count / 4);
112 		if (!is_suspended(retval))
113 			break;
114 
115 		/* resume from suspend */
116 		while (snd_pcm_resume(pcm_handle) == -EAGAIN)
117 			sleep(1);
118 	} while (1);
119 	if (retval < 0) {
120 		fprintf(stderr, _("snd_pcm_writei failed: %s\n"), snd_strerror(retval));
121 		snd_pcm_prepare(pcm_handle);
122 	}
123 	return retval;
124 }
125 
alsa_close()126 static void regparm alsa_close()
127 {
128 	snd_pcm_drop(pcm_handle);
129 	snd_pcm_close(pcm_handle);
130 }
131 
132 const struct output_plugin plugout_alsa = {
133 	.name = "alsa",
134 	.description = "ALSA sound driver",
135 	.open = alsa_open,
136 	.write = alsa_write,
137 	.close = alsa_close,
138 };
139