2 Copyright (C) 2006 Mathieu Olivier
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.
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.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to:
18 Free Software Foundation, Inc.
19 59 Temple Place - Suite 330
20 Boston, MA 02111-1307, USA
24 // ALSA module, used by Linux
27 #include <alsa/asoundlib.h>
35 static snd_pcm_t* pcm_handle = NULL;
36 static snd_pcm_sframes_t expected_delay = 0;
37 static unsigned int alsasoundtime;
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
48 qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested)
52 snd_pcm_hw_params_t* hw_params = NULL;
53 snd_pcm_format_t snd_pcm_format;
54 snd_pcm_uframes_t buffer_size;
56 Con_Print ("SndSys_Init: using the ALSA module\n");
58 // Check the requested sound format
59 if (requested->width < 1 || requested->width > 2)
61 Con_Printf ("SndSys_Init: invalid sound width (%hu)\n",
64 if (suggested != NULL)
66 memcpy (suggested, requested, sizeof (suggested));
68 if (requested->width < 1)
73 Con_Printf ("SndSys_Init: suggesting sound width = %hu\n",
80 if (pcm_handle != NULL)
82 Con_Print ("SndSys_Init: WARNING: Init called before Shutdown!\n");
86 // Determine the name of the PCM handle we'll use
87 switch (requested->channels)
90 pcm_name = "surround40";
93 pcm_name = "surround51";
96 pcm_name = "surround71";
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];
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);
112 Con_Printf ("SndSys_Init: can't open audio device \"%s\" (%s)\n",
113 pcm_name, snd_strerror (err));
117 // Allocate the hardware parameters
118 err = snd_pcm_hw_params_malloc (&hw_params);
121 Con_Printf ("SndSys_Init: can't allocate hardware parameters (%s)\n",
125 err = snd_pcm_hw_params_any (pcm_handle, hw_params);
128 Con_Printf ("SndSys_Init: can't initialize hardware parameters (%s)\n",
133 // Set the access type
134 err = snd_pcm_hw_params_set_access (pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED);
137 Con_Printf ("SndSys_Init: can't set access type (%s)\n",
142 // Set the sound width
143 if (requested->width == 1)
144 snd_pcm_format = SND_PCM_FORMAT_U8;
146 snd_pcm_format = SND_PCM_FORMAT_S16;
147 err = snd_pcm_hw_params_set_format (pcm_handle, hw_params, snd_pcm_format);
150 Con_Printf ("SndSys_Init: can't set sound width to %hu (%s)\n",
151 requested->width, snd_strerror (err));
155 // Set the sound channels
156 err = snd_pcm_hw_params_set_channels (pcm_handle, hw_params, requested->channels);
159 Con_Printf ("SndSys_Init: can't set sound channels to %hu (%s)\n",
160 requested->channels, snd_strerror (err));
164 // Set the sound speed
165 err = snd_pcm_hw_params_set_rate (pcm_handle, hw_params, requested->speed, 0);
168 Con_Printf ("SndSys_Init: can't set sound speed to %u (%s)\n",
169 requested->speed, snd_strerror (err));
173 buffer_size = requested->speed / 5;
174 err = snd_pcm_hw_params_set_buffer_size_near (pcm_handle, hw_params, &buffer_size);
177 Con_Printf ("SndSys_Init: can't set sound buffer size to %lu (%s)\n",
178 buffer_size, snd_strerror (err));
182 buffer_size /= NB_PERIODS;
183 err = snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_params, &buffer_size, 0);
186 Con_Printf ("SndSys_Init: can't set sound period size to %lu (%s)\n",
187 buffer_size, snd_strerror (err));
191 err = snd_pcm_hw_params (pcm_handle, hw_params);
194 Con_Printf ("SndSys_Init: can't set hardware parameters (%s)\n",
199 snd_pcm_hw_params_free (hw_params);
201 snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL);
204 if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO)
205 Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_ALSA);
210 // It's not very clean, but it avoids a lot of duplicated code.
213 if (hw_params != NULL)
214 snd_pcm_hw_params_free (hw_params);
224 Stop the sound card, delete "snd_renderbuffer" and free its other resources
227 void SndSys_Shutdown (void)
229 if (pcm_handle != NULL)
231 snd_pcm_close(pcm_handle);
235 if (snd_renderbuffer != NULL)
237 Mem_Free(snd_renderbuffer->ring);
238 Mem_Free(snd_renderbuffer);
239 snd_renderbuffer = NULL;
248 Try to recover from errors
251 static qboolean SndSys_Recover (int err_num)
255 // We can only do something on underrun ("broken pipe") errors
256 if (err_num != -EPIPE)
259 err = snd_pcm_prepare (pcm_handle);
262 Con_DPrintf ("SndSys_Recover: unable to recover (%s)\n",
265 // TOCHECK: should we stop the playback ?
279 static snd_pcm_sframes_t SndSys_Write (const unsigned char* buffer, unsigned int nbframes)
281 snd_pcm_sframes_t written;
283 written = snd_pcm_writei (pcm_handle, buffer, nbframes);
286 if (developer.integer >= 100)
287 Con_Printf ("SndSys_Write: audio write returned %ld (%s)!\n",
288 written, snd_strerror (written));
290 if (SndSys_Recover (written))
292 written = snd_pcm_writei (pcm_handle, buffer, nbframes);
294 Con_DPrintf ("SndSys_Write: audio write failed again (error %ld: %s)!\n",
295 written, snd_strerror (written));
307 Submit the contents of "snd_renderbuffer" to the sound card
310 void SndSys_Submit (void)
312 unsigned int startoffset, factor;
313 snd_pcm_uframes_t limit, nbframes;
314 snd_pcm_sframes_t written;
316 if (pcm_handle == NULL ||
317 snd_renderbuffer->startframe == snd_renderbuffer->endframe)
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;
325 if (nbframes > limit)
327 written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], limit);
330 snd_renderbuffer->startframe += written;
331 expected_delay += written;
333 if ((snd_pcm_uframes_t)written != limit)
340 written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], nbframes);
344 snd_renderbuffer->startframe += written;
345 expected_delay += written;
353 Returns the number of sample frames consumed since the sound started
356 unsigned int SndSys_GetSoundTime (void)
358 snd_pcm_sframes_t delay, timediff;
361 if (pcm_handle == NULL)
364 err = snd_pcm_delay (pcm_handle, &delay);
367 if (developer.integer >= 100)
368 Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay (%s)\n",
371 if (! SndSys_Recover (err))
374 err = snd_pcm_delay (pcm_handle, &delay);
377 Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay, again (%s)\n",
383 if (expected_delay < delay)
385 Con_DPrintf ("SndSys_GetSoundTime: expected_delay(%ld) < delay(%ld)\n",
386 expected_delay, delay);
390 timediff = expected_delay - delay;
391 expected_delay = delay;
393 alsasoundtime += (unsigned int)timediff;
395 return alsasoundtime;
401 SndSys_LockRenderBuffer
403 Get the exclusive lock on "snd_renderbuffer"
406 qboolean SndSys_LockRenderBuffer (void)
415 SndSys_UnlockRenderBuffer
417 Release the exclusive lock on "snd_renderbuffer"
420 void SndSys_UnlockRenderBuffer (void)