]> icculus.org git repositories - divverent/darkplaces.git/blob - snd_alsa.c
don't crash if a quake button entity is on its alternate frames but doesn't actually...
[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         alsaspeakerlayout = true;
205         
206         return true;
207
208
209 // It's not very clean, but it avoids a lot of duplicated code.
210 init_error:
211         
212         if (hw_params != NULL)
213                 snd_pcm_hw_params_free (hw_params);
214         SndSys_Shutdown ();
215         return false;
216 }
217
218
219 /*
220 ====================
221 SndSys_Shutdown
222
223 Stop the sound card, delete "snd_renderbuffer" and free its other resources
224 ====================
225 */
226 void SndSys_Shutdown (void)
227 {
228         if (pcm_handle != NULL)
229         {
230                 snd_pcm_close(pcm_handle);
231                 pcm_handle = NULL;
232         }
233
234         if (snd_renderbuffer != NULL)
235         {
236                 Mem_Free(snd_renderbuffer->ring);
237                 Mem_Free(snd_renderbuffer);
238                 snd_renderbuffer = NULL;
239         }
240 }
241
242
243 /*
244 ====================
245 SndSys_Recover
246
247 Try to recover from errors
248 ====================
249 */
250 static qboolean SndSys_Recover (int err_num)
251 {
252         int err;
253
254         // We can only do something on underrun ("broken pipe") errors
255         if (err_num != -EPIPE)
256                 return false;
257                         
258         err = snd_pcm_prepare (pcm_handle);
259         if (err != 0)
260         {
261                 Con_DPrintf ("SndSys_Recover: unable to recover (%s)\n",
262                                          snd_strerror (err));
263
264                 // TOCHECK: should we stop the playback ?
265
266                 return false;
267         }
268         
269         Con_DPrint ("SndSys_Recover: recovered successfully\n");
270
271         return true;
272 }
273
274
275 /*
276 ====================
277 SndSys_Write
278 ====================
279 */
280 static snd_pcm_sframes_t SndSys_Write (const unsigned char* buffer, unsigned int nbframes)
281 {
282         snd_pcm_sframes_t written;
283
284         written = snd_pcm_writei (pcm_handle, buffer, nbframes);
285         if (written < 0)
286         {
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_Printf ("SndSys_Write: audio write failed again (error %ld: %s)!\n",
295                                                         written, snd_strerror (written));
296                 }
297         }
298         
299         return written;
300 }
301
302
303 /*
304 ====================
305 SndSys_Submit
306
307 Submit the contents of "snd_renderbuffer" to the sound card
308 ====================
309 */
310 void SndSys_Submit (void)
311 {
312         unsigned int startoffset, factor;
313         snd_pcm_uframes_t limit, nbframes;
314         snd_pcm_sframes_t written;
315         
316         if (pcm_handle == NULL ||
317                 snd_renderbuffer->startframe == snd_renderbuffer->endframe)
318                 return;
319
320         startoffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes;
321         factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels;
322         limit = snd_renderbuffer->maxframes - startoffset;
323         nbframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe;
324 //Con_DPrintf(">> SndSys_Submit: startframe=%u, endframe=%u (%u frames), maxframes=%u, startoffset=%u\n",
325 //                      snd_renderbuffer->startframe, snd_renderbuffer->endframe,
326 //                      nbframes, snd_renderbuffer->maxframes, startoffset);
327
328         if (nbframes > limit)
329         {
330 //Con_DPrintf(">> SndSys_Submit: 2 phases-submit\n");
331                 written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], limit);
332                 if (written < 0)
333                         return;
334                 snd_renderbuffer->startframe += written;
335                 expected_delay += written;
336
337 //Con_DPrintf(">> SndSys_Submit: %ld/%ld frames written\n", written, limit);
338                 if ((snd_pcm_uframes_t)written != limit)
339                         return;
340                 
341                 nbframes -= limit;
342                 startoffset = 0;
343         }
344 //else Con_DPrintf(">> SndSys_Submit: 1 phase-submit\n");
345
346         written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], nbframes);
347         if (written < 0)
348                 return;
349 //Con_DPrintf(">> SndSys_Submit: %ld/%ld frames written\n", written, nbframes);
350         snd_renderbuffer->startframe += written;
351         expected_delay += written;
352 }
353
354
355 /*
356 ====================
357 SndSys_GetSoundTime
358
359 Returns the number of sample frames consumed since the sound started
360 ====================
361 */
362 unsigned int SndSys_GetSoundTime (void)
363 {
364         snd_pcm_sframes_t delay, timediff;
365         int err;
366
367         if (pcm_handle == NULL)
368                 return 0;
369
370         err = snd_pcm_delay (pcm_handle, &delay);
371         if (err != 0)
372         {
373                 Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay (%s)\n",
374                                          snd_strerror (err));
375
376                 if (! SndSys_Recover (err))
377                         return 0;
378
379                 err = snd_pcm_delay (pcm_handle, &delay);
380                 if (err != 0)
381                 {
382                         Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay, again (%s)\n",
383                                                  snd_strerror (err));
384                         return 0;
385                 }
386         }
387
388 //Con_DPrintf(">> SndSys_GetSoundTime: expected_delay=%ld, delay=%ld\n",
389 //                      expected_delay, delay);
390         if (expected_delay < delay)
391         {
392                 Con_Printf ("SndSys_GetSoundTime: expected_delay(%ld) < delay(%ld)\n",
393                                         expected_delay, delay);
394                 timediff = 0;
395         }
396         else
397                 timediff = expected_delay - delay;
398         expected_delay = delay;
399         
400         alsasoundtime += (unsigned int)timediff;
401         
402         return alsasoundtime;
403 }
404
405
406 /*
407 ====================
408 SndSys_LockRenderBuffer
409
410 Get the exclusive lock on "snd_renderbuffer"
411 ====================
412 */
413 qboolean SndSys_LockRenderBuffer (void)
414 {
415         // Nothing to do
416         return true;
417 }
418
419
420 /*
421 ====================
422 SndSys_UnlockRenderBuffer
423
424 Release the exclusive lock on "snd_renderbuffer"
425 ====================
426 */
427 void SndSys_UnlockRenderBuffer (void)
428 {
429         // Nothing to do
430 }