1 /** \file soundalsa.c
2 * \brief Implementation of the ALSA sound device
3 *
4 * \author Dag Lem <resid@nimrod.no>
5 *
6 * based on ALSA /test/pcm.c and various scarce documentation.
7 */
8
9 /*
10 * This file is part of VICE, the Versatile Commodore Emulator.
11 * See README for copyright notice.
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
26 * 02111-1307 USA.
27 *
28 */
29
30 #include "vice.h"
31
32 #ifdef USE_ALSA
33
34 #define ALSA_PCM_NEW_HW_PARAMS_API
35
36 #include "alsa/asoundlib.h"
37 #include "debug.h"
38 #include "log.h"
39 #include "sound.h"
40
41 /* NetBSD doesn't define ESTRPIPE, this fix I noticed in gstreamer code */
42 #ifndef ESTRPIPE
43 #define ESTRPIPE EPIPE
44 #endif
45
46 static snd_pcm_t *handle;
47 static int alsa_bufsize;
48 static int alsa_fragsize;
49 static int alsa_channels;
50 static int alsa_can_pause;
51
alsa_init(const char * param,int * speed,int * fragsize,int * fragnr,int * channels)52 static int alsa_init(const char *param, int *speed, int *fragsize, int *fragnr, int *channels)
53 {
54 int err, dir;
55 unsigned int rate, periods;
56 snd_pcm_uframes_t period_size;
57 snd_pcm_hw_params_t *hwparams;
58
59 if (!param) {
60 param = "default";
61 }
62
63 snd_pcm_hw_params_alloca(&hwparams);
64
65 if ((err = snd_pcm_open(&handle, param, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
66 log_message(LOG_DEFAULT, "Playback open error for '%s': %s", param,
67 snd_strerror(err));
68 return 1;
69 }
70
71 if ((err = snd_pcm_hw_params_any(handle, hwparams)) < 0) {
72 log_message(LOG_DEFAULT, "Broken configuration for playback: no configurations available: %s", snd_strerror(err));
73 goto fail;
74 }
75
76 if ((err = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
77 log_message(LOG_DEFAULT, "Access type not available for playback: %s", snd_strerror(err));
78 goto fail;
79 }
80
81 if ((err = snd_pcm_hw_params_set_format(handle, hwparams, SND_PCM_FORMAT_S16)) < 0) {
82 log_message(LOG_DEFAULT, "Sample format not available for playback: %s", snd_strerror(err));
83 goto fail;
84 }
85
86 if ((err = snd_pcm_hw_params_set_channels(handle, hwparams,
87 (unsigned int)*channels)) < 0) {
88 log_message(LOG_DEFAULT, "Channels count (%i) not available for playbacks: %s", *channels, snd_strerror(err));
89 goto fail;
90 }
91
92 rate = (unsigned int)*speed;
93 if ((err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &rate, 0)) < 0) {
94 log_message(LOG_DEFAULT, "Rate %iHz not available for playback: %s", *speed, snd_strerror(err));
95 goto fail;
96 }
97 if (rate != (unsigned int)*speed) {
98 printf("Rate doesn't match (requested %iHz, got %uHz)", *speed, rate);
99 *speed = (int)rate;
100 }
101 /* calculate requested buffer size */
102 alsa_bufsize = (*fragsize) * (*fragnr);
103
104 period_size = (snd_pcm_uframes_t)*fragsize;
105 dir = 0;
106 if ((err = snd_pcm_hw_params_set_period_size_near(handle, hwparams, &period_size, &dir)) < 0) {
107 log_message(LOG_DEFAULT, "Unable to set period size %lu for playback: %s",
108 period_size, snd_strerror(err));
109 goto fail;
110 }
111 *fragsize = (int)period_size;
112
113 /* number of periods according to the buffer size we wanted, nearest val */
114 *fragnr = (alsa_bufsize + *fragsize / 2) / *fragsize;
115
116 periods = (unsigned int)*fragnr;
117 dir = 0;
118 if ((err = snd_pcm_hw_params_set_periods_near(handle, hwparams, &periods, &dir)) < 0) {
119 log_message(LOG_DEFAULT, "Unable to set periods %u for playback: %s",
120 periods, snd_strerror(err));
121 goto fail;
122 }
123 *fragnr = (int)periods;
124
125 alsa_can_pause = snd_pcm_hw_params_can_pause(hwparams);
126
127 if ((err = snd_pcm_hw_params(handle, hwparams)) < 0) {
128 log_message(LOG_DEFAULT, "Unable to set hw params for playback: %s", snd_strerror(err));
129 goto fail;
130 }
131
132 alsa_bufsize = (*fragsize) * (*fragnr);
133 alsa_fragsize = *fragsize;
134 alsa_channels = *channels;
135
136 return 0;
137
138 fail:
139 snd_pcm_close(handle);
140 handle = NULL;
141 return 1;
142 }
143
xrun_recovery(snd_pcm_t * hnd,int err)144 static int xrun_recovery(snd_pcm_t *hnd, int err)
145 {
146 if (err == -EPIPE) { /* under-run */
147 if ((err = snd_pcm_prepare(hnd)) < 0) {
148 log_message(LOG_DEFAULT, "Can't recover from underrun, prepare failed: %s", snd_strerror(err));
149 }
150 return 0;
151 } else if (err == -ESTRPIPE) {
152 while ((err = snd_pcm_resume(hnd)) == -EAGAIN) {
153 log_message(LOG_DEFAULT, "xrun_recovery: %s", snd_strerror(err));
154 sleep(1); /* wait until the suspend flag is released */
155 }
156 if (err < 0) {
157 if ((err = snd_pcm_prepare(hnd)) < 0) {
158 log_message(LOG_DEFAULT, "Can't recover from suspend, prepare failed: %s", snd_strerror(err));
159 }
160 }
161 return 0;
162 }
163 return err;
164 }
165
alsa_write(int16_t * pbuf,size_t nr)166 static int alsa_write(int16_t *pbuf, size_t nr)
167 {
168 int err;
169
170 nr /= (size_t)alsa_channels;
171
172 while (nr > 0) {
173 err = (int)snd_pcm_writei(handle, pbuf, nr);
174 if (err == -EAGAIN) {
175 log_message(LOG_DEFAULT, "Write error: %s", snd_strerror(err));
176 continue;
177 } else if (err < 0 && (err = xrun_recovery(handle, err)) < 0) {
178 log_message(LOG_DEFAULT, "Write error: %s", snd_strerror(err));
179 return 1;
180 }
181 pbuf += err * alsa_channels;
182 nr -= (size_t)err;
183 }
184
185 return 0;
186 }
187
alsa_bufferspace(void)188 static int alsa_bufferspace(void)
189 {
190 #ifdef HAVE_SND_PCM_AVAIL
191 snd_pcm_sframes_t space = snd_pcm_avail(handle);
192 #else
193 snd_pcm_sframes_t space = snd_pcm_avail_update(handle);
194 #endif
195 /* keep alsa values real. Values < 0 mean errors, call to alsa_write
196 * will resume. */
197 if (space < 0 || space > alsa_bufsize) {
198 space = alsa_bufsize;
199 }
200 return (int)space;
201 }
202
alsa_close(void)203 static void alsa_close(void)
204 {
205 snd_pcm_close(handle);
206 handle = NULL;
207 alsa_bufsize = 0;
208 alsa_fragsize = 0;
209 }
210
alsa_suspend(void)211 static int alsa_suspend(void)
212 {
213 int err;
214
215 if (!alsa_can_pause) {
216 return 1;
217 }
218
219 if ((err = snd_pcm_pause(handle, 1)) < 0) {
220 log_message(LOG_DEFAULT, "Unable to pause playback: %s", snd_strerror(err));
221 return 1;
222 }
223
224 return 0;
225 }
226
alsa_resume(void)227 static int alsa_resume(void)
228 {
229 int err;
230
231 if (!alsa_can_pause) {
232 return 1;
233 }
234
235 if ((err = snd_pcm_pause(handle, 0)) < 0) {
236 log_message(LOG_DEFAULT, "Unable to resume playback: %s", snd_strerror(err));
237 return 1;
238 }
239
240 return 0;
241 }
242
243 static sound_device_t alsa_device =
244 {
245 "alsa",
246 alsa_init,
247 alsa_write,
248 NULL,
249 NULL,
250 alsa_bufferspace,
251 alsa_close,
252 alsa_suspend,
253 alsa_resume,
254 1,
255 2,
256 true
257 };
258
sound_init_alsa_device(void)259 int sound_init_alsa_device(void)
260 {
261 return sound_register_device(&alsa_device);
262 }
263 #endif
264