Removed some debug printings, and made some others requiring developer >= 100. Reset...
[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         return true;
270 }
271
272
273 /*
274 ====================
275 SndSys_Write
276 ====================
277 */
278 static snd_pcm_sframes_t SndSys_Write (const unsigned char* buffer, unsigned int nbframes)
279 {
280         snd_pcm_sframes_t written;
281
282         written = snd_pcm_writei (pcm_handle, buffer, nbframes);
283         if (written < 0)
284         {
285                 if (developer.integer >= 100)
286                         Con_Printf ("SndSys_Write: audio write returned %ld (%s)!\n",
287                                                  written, snd_strerror (written));
288
289                 if (SndSys_Recover (written))
290                 {
291                         written = snd_pcm_writei (pcm_handle, buffer, nbframes);
292                         if (written < 0)
293                                 Con_DPrintf ("SndSys_Write: audio write failed again (error %ld: %s)!\n",
294                                                          written, snd_strerror (written));
295                 }
296         }
297         
298         return written;
299 }
300
301
302 /*
303 ====================
304 SndSys_Submit
305
306 Submit the contents of "snd_renderbuffer" to the sound card
307 ====================
308 */
309 void SndSys_Submit (void)
310 {
311         unsigned int startoffset, factor;
312         snd_pcm_uframes_t limit, nbframes;
313         snd_pcm_sframes_t written;
314         
315         if (pcm_handle == NULL ||
316                 snd_renderbuffer->startframe == snd_renderbuffer->endframe)
317                 return;
318
319         startoffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes;
320         factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels;
321         limit = snd_renderbuffer->maxframes - startoffset;
322         nbframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe;
323
324         if (nbframes > limit)
325         {
326                 written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], limit);
327                 if (written < 0)
328                         return;
329                 snd_renderbuffer->startframe += written;
330                 expected_delay += written;
331
332                 if ((snd_pcm_uframes_t)written != limit)
333                         return;
334                 
335                 nbframes -= limit;
336                 startoffset = 0;
337         }
338
339         written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], nbframes);
340         if (written < 0)
341                 return;
342
343         snd_renderbuffer->startframe += written;
344         expected_delay += written;
345 }
346
347
348 /*
349 ====================
350 SndSys_GetSoundTime
351
352 Returns the number of sample frames consumed since the sound started
353 ====================
354 */
355 unsigned int SndSys_GetSoundTime (void)
356 {
357         snd_pcm_sframes_t delay, timediff;
358         int err;
359
360         if (pcm_handle == NULL)
361                 return 0;
362
363         err = snd_pcm_delay (pcm_handle, &delay);
364         if (err != 0)
365         {
366                 if (developer.integer >= 100)
367                         Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay (%s)\n",
368                                                  snd_strerror (err));
369
370                 if (! SndSys_Recover (err))
371                         return 0;
372
373                 err = snd_pcm_delay (pcm_handle, &delay);
374                 if (err != 0)
375                 {
376                         Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay, again (%s)\n",
377                                                  snd_strerror (err));
378                         return 0;
379                 }
380         }
381
382         if (expected_delay < delay)
383         {
384                 Con_DPrintf ("SndSys_GetSoundTime: expected_delay(%ld) < delay(%ld)\n",
385                                          expected_delay, delay);
386                 timediff = 0;
387         }
388         else
389                 timediff = expected_delay - delay;
390         expected_delay = delay;
391         
392         alsasoundtime += (unsigned int)timediff;
393         
394         return alsasoundtime;
395 }
396
397
398 /*
399 ====================
400 SndSys_LockRenderBuffer
401
402 Get the exclusive lock on "snd_renderbuffer"
403 ====================
404 */
405 qboolean SndSys_LockRenderBuffer (void)
406 {
407         // Nothing to do
408         return true;
409 }
410
411
412 /*
413 ====================
414 SndSys_UnlockRenderBuffer
415
416 Release the exclusive lock on "snd_renderbuffer"
417 ====================
418 */
419 void SndSys_UnlockRenderBuffer (void)
420 {
421         // Nothing to do
422 }