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