a1d0c26241017091478e190c78ce93aa75c45cea
[divverent/darkplaces.git] / snd_alsa.c
1 /*
2         Copyright (C) 2006  Mathieu Olivier
3
4         This program is free software; you can redistribute it and/or
5         modify it under the terms of the GNU General Public License
6         as published by the Free Software Foundation; either version 2
7         of the License, or (at your option) any later version.
8
9         This program is distributed in the hope that it will be useful,
10         but WITHOUT ANY WARRANTY; without even the implied warranty of
11         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13         See the GNU General Public License for more details.
14
15         You should have received a copy of the GNU General Public License
16         along with this program; if not, write to:
17
18                 Free Software Foundation, Inc.
19                 59 Temple Place - Suite 330
20                 Boston, MA  02111-1307, USA
21
22 */
23
24 // ALSA module, used by Linux
25
26
27 #include <alsa/asoundlib.h>
28
29 #include "quakedef.h"
30 #include "snd_main.h"
31
32
33 #define NB_PERIODS 2
34
35 static snd_pcm_t* pcm_handle = NULL;
36 static snd_pcm_sframes_t expected_delay = 0;
37 static unsigned int alsasoundtime;
38
39
40 /*
41 ====================
42 SndSys_Init
43
44 Create "snd_renderbuffer" with the proper sound format if the call is successful
45 May return a suggested format if the requested format isn't available
46 ====================
47 */
48 qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested)
49 {
50         const char* pcm_name;
51         int i, err;
52         snd_pcm_hw_params_t* hw_params = NULL;
53         snd_pcm_format_t snd_pcm_format;
54         snd_pcm_uframes_t buffer_size;
55
56         Con_Print ("SndSys_Init: using the ALSA module\n");
57
58         // Check the requested sound format
59         if (requested->width < 1 || requested->width > 2)
60         {
61                 Con_Printf ("SndSys_Init: invalid sound width (%hu)\n",
62                                         requested->width);
63
64                 if (suggested != NULL)
65                 {
66                         memcpy (suggested, requested, sizeof (suggested));
67
68                         if (requested->width < 1)
69                                 suggested->width = 1;
70                         else
71                                 suggested->width = 2;
72
73                         Con_Printf ("SndSys_Init: suggesting sound width = %hu\n",
74                                                 suggested->width);
75                 }
76                 
77                 return false;
78     }
79         
80         if (pcm_handle != NULL)
81         {
82                 Con_Print ("SndSys_Init: WARNING: Init called before Shutdown!\n");
83                 SndSys_Shutdown ();
84         }
85         
86         // Determine the name of the PCM handle we'll use
87         switch (requested->channels)
88         {
89                 case 4:
90                         pcm_name = "surround40";
91                         break;
92                 case 6:
93                         pcm_name = "surround51";
94                         break;
95                 case 8:
96                         pcm_name = "surround71";
97                         break;
98                 default:
99                         pcm_name = "default";
100                         break;
101         }
102 // COMMANDLINEOPTION: Linux ALSA Sound: -sndpcm <devicename> selects which pcm device to us, default is "default"
103         i = COM_CheckParm ("-sndpcm");
104         if (i != 0 && i < com_argc - 1)
105                 pcm_name = com_argv[i + 1];
106
107         // Open the audio device
108         Con_DPrintf ("SndSys_Init: PCM device is \"%s\"\n", pcm_name);
109         err = snd_pcm_open (&pcm_handle, pcm_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
110         if (err != 0)
111         {
112                 Con_Printf ("SndSys_Init: can't open audio device \"%s\" (%s)\n",
113                                         pcm_name, snd_strerror (err));
114                 return false;
115         }
116         
117         // Allocate the hardware parameters
118         err = snd_pcm_hw_params_malloc (&hw_params);
119         if (err != 0)
120         {
121                 Con_Printf ("SndSys_Init: can't allocate hardware parameters (%s)\n",
122                                         snd_strerror (err));
123                 goto init_error;
124         }
125         err = snd_pcm_hw_params_any (pcm_handle, hw_params);
126         if (err != 0)
127         {
128                 Con_Printf ("SndSys_Init: can't initialize hardware parameters (%s)\n",
129                                         snd_strerror (err));
130                 goto init_error;
131         }
132
133         // Set the access type
134         err = snd_pcm_hw_params_set_access (pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
135         if (err != 0)
136         {
137                 Con_Printf ("SndSys_Init: can't set access type (%s)\n",
138                                         snd_strerror (err));
139                 goto init_error;
140         }
141
142         // Set the sound width
143         if (requested->width == 1)
144                 snd_pcm_format = SND_PCM_FORMAT_U8;
145         else
146                 snd_pcm_format = SND_PCM_FORMAT_S16;
147         err = snd_pcm_hw_params_set_format (pcm_handle, hw_params, snd_pcm_format);
148         if (err != 0)
149         {
150                 Con_Printf ("SndSys_Init: can't set sound width to %hu (%s)\n",
151                                         requested->width, snd_strerror (err));
152                 goto init_error;
153         }
154
155         // Set the sound channels
156         err = snd_pcm_hw_params_set_channels (pcm_handle, hw_params, requested->channels);
157         if (err != 0)
158         {
159                 Con_Printf ("SndSys_Init: can't set sound channels to %hu (%s)\n",
160                                         requested->channels, snd_strerror (err));
161                 goto init_error;
162         }
163
164         // Set the sound speed
165         err = snd_pcm_hw_params_set_rate (pcm_handle, hw_params, requested->speed, 0);
166         if (err != 0)
167         {
168                 Con_Printf ("SndSys_Init: can't set sound speed to %u (%s)\n",
169                                         requested->speed, snd_strerror (err));
170                 goto init_error;
171         }
172
173         buffer_size = requested->speed / 5;
174         err = snd_pcm_hw_params_set_buffer_size_near (pcm_handle, hw_params, &buffer_size);
175         if (err != 0)
176         {
177                 Con_Printf ("SndSys_Init: can't set sound buffer size to %lu (%s)\n",
178                                         buffer_size, snd_strerror (err));
179                 goto init_error;
180         }
181
182         buffer_size /= NB_PERIODS;
183         err = snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_params, &buffer_size, 0);
184         if (err != 0)
185         {
186                 Con_Printf ("SndSys_Init: can't set sound period size to %lu (%s)\n",
187                                         buffer_size, snd_strerror (err));
188                 goto init_error;
189         }
190
191         err = snd_pcm_hw_params (pcm_handle, hw_params);
192         if (err != 0)
193         {
194                 Con_Printf ("SndSys_Init: can't set hardware parameters (%s)\n",
195                                         snd_strerror (err));
196                 goto init_error;
197         }
198
199         snd_pcm_hw_params_free (hw_params);
200
201         snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL);
202         expected_delay = 0;
203         alsasoundtime = 0;
204         if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO)
205                 Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_ALSA);
206         
207         return true;
208
209
210 // It's not very clean, but it avoids a lot of duplicated code.
211 init_error:
212         
213         if (hw_params != NULL)
214                 snd_pcm_hw_params_free (hw_params);
215         SndSys_Shutdown ();
216         return false;
217 }
218
219
220 /*
221 ====================
222 SndSys_Shutdown
223
224 Stop the sound card, delete "snd_renderbuffer" and free its other resources
225 ====================
226 */
227 void SndSys_Shutdown (void)
228 {
229         if (pcm_handle != NULL)
230         {
231                 snd_pcm_close(pcm_handle);
232                 pcm_handle = NULL;
233         }
234
235         if (snd_renderbuffer != NULL)
236         {
237                 Mem_Free(snd_renderbuffer->ring);
238                 Mem_Free(snd_renderbuffer);
239                 snd_renderbuffer = NULL;
240         }
241 }
242
243
244 /*
245 ====================
246 SndSys_Recover
247
248 Try to recover from errors
249 ====================
250 */
251 static qboolean SndSys_Recover (int err_num)
252 {
253         int err;
254
255         // We can only do something on underrun ("broken pipe") errors
256         if (err_num != -EPIPE)
257                 return false;
258                         
259         err = snd_pcm_prepare (pcm_handle);
260         if (err != 0)
261         {
262                 Con_DPrintf ("SndSys_Recover: unable to recover (%s)\n",
263                                          snd_strerror (err));
264
265                 // TOCHECK: should we stop the playback ?
266
267                 return false;
268         }
269
270         return true;
271 }
272
273
274 /*
275 ====================
276 SndSys_Write
277 ====================
278 */
279 static snd_pcm_sframes_t SndSys_Write (const unsigned char* buffer, unsigned int nbframes)
280 {
281         snd_pcm_sframes_t written;
282
283         written = snd_pcm_writei (pcm_handle, buffer, nbframes);
284         if (written < 0)
285         {
286                 if (developer.integer >= 1000 && vid_activewindow)
287                         Con_Printf ("SndSys_Write: audio write returned %ld (%s)!\n",
288                                                  written, snd_strerror (written));
289
290                 if (SndSys_Recover (written))
291                 {
292                         written = snd_pcm_writei (pcm_handle, buffer, nbframes);
293                         if (written < 0)
294                                 Con_DPrintf ("SndSys_Write: audio write failed again (error %ld: %s)!\n",
295                                                          written, snd_strerror (written));
296                 }
297         }
298         if (written > 0)
299         {
300                 snd_renderbuffer->startframe += written;
301                 expected_delay += written;
302         }
303         
304         return written;
305 }
306
307
308 /*
309 ====================
310 SndSys_Submit
311
312 Submit the contents of "snd_renderbuffer" to the sound card
313 ====================
314 */
315 void SndSys_Submit (void)
316 {
317         unsigned int startoffset, factor;
318         snd_pcm_uframes_t limit, nbframes;
319         snd_pcm_sframes_t written;
320         
321         if (pcm_handle == NULL ||
322                 snd_renderbuffer->startframe == snd_renderbuffer->endframe)
323                 return;
324
325         startoffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes;
326         factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels;
327         limit = snd_renderbuffer->maxframes - startoffset;
328         nbframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe;
329
330         if (nbframes > limit)
331         {
332                 written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], limit);
333                 if (written < 0 || (snd_pcm_uframes_t)written != limit)
334                         return;
335                 
336                 nbframes -= limit;
337                 startoffset = 0;
338         }
339
340         written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], nbframes);
341         if (written < 0)
342                 return;
343 }
344
345
346 /*
347 ====================
348 SndSys_GetSoundTime
349
350 Returns the number of sample frames consumed since the sound started
351 ====================
352 */
353 unsigned int SndSys_GetSoundTime (void)
354 {
355         snd_pcm_sframes_t delay, timediff;
356         int err;
357
358         if (pcm_handle == NULL)
359                 return 0;
360
361         err = snd_pcm_delay (pcm_handle, &delay);
362         if (err != 0)
363         {
364                 if (developer.integer >= 1000 && vid_activewindow)
365                         Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay (%s)\n",
366                                                  snd_strerror (err));
367
368                 if (! SndSys_Recover (err))
369                         return 0;
370
371                 err = snd_pcm_delay (pcm_handle, &delay);
372                 if (err != 0)
373                 {
374                         Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay, again (%s)\n",
375                                                  snd_strerror (err));
376                         return 0;
377                 }
378         }
379
380         if (expected_delay < delay)
381         {
382                 Con_DPrintf ("SndSys_GetSoundTime: expected_delay(%ld) < delay(%ld)\n",
383                                          expected_delay, delay);
384                 timediff = 0;
385         }
386         else
387                 timediff = expected_delay - delay;
388         expected_delay = delay;
389         
390         alsasoundtime += (unsigned int)timediff;
391         
392         return alsasoundtime;
393 }
394
395
396 /*
397 ====================
398 SndSys_LockRenderBuffer
399
400 Get the exclusive lock on "snd_renderbuffer"
401 ====================
402 */
403 qboolean SndSys_LockRenderBuffer (void)
404 {
405         // Nothing to do
406         return true;
407 }
408
409
410 /*
411 ====================
412 SndSys_UnlockRenderBuffer
413
414 Release the exclusive lock on "snd_renderbuffer"
415 ====================
416 */
417 void SndSys_UnlockRenderBuffer (void)
418 {
419         // Nothing to do
420 }