031185b6b14fd6ddc3f6d9fe58c9770f1f419837
[xonotic/darkplaces.git] / snd_alsa.c
1 /*
2         snd_alsa.c
3
4         Support for the ALSA 1.0.1 sound driver
5
6         Copyright (C) 1999,2000  contributors of the QuakeForge project
7         Please see the file "AUTHORS" for a list of contributors
8
9         This program is free software; you can redistribute it and/or
10         modify it under the terms of the GNU General Public License
11         as published by the Free Software Foundation; either version 2
12         of the License, or (at your option) any later version.
13
14         This program is distributed in the hope that it will be useful,
15         but WITHOUT ANY WARRANTY; without even the implied warranty of
16         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18         See the GNU General Public License for more details.
19
20         You should have received a copy of the GNU General Public License
21         along with this program; if not, write to:
22
23                 Free Software Foundation, Inc.
24                 59 Temple Place - Suite 330
25                 Boston, MA  02111-1307, USA
26
27 */
28
29 #include <alsa/asoundlib.h>
30
31 #include "quakedef.h"
32
33 static int                      snd_inited;
34 static snd_pcm_uframes_t buffer_size;
35
36 static const char  *pcmname = NULL;
37 static snd_pcm_t   *pcm;
38
39 qboolean SNDDMA_Init (void)
40 {
41         int                                      err, i;
42         int                                      bps = -1, stereo = -1;
43         unsigned int             rate = 0;
44         snd_pcm_hw_params_t     *hw;
45         snd_pcm_sw_params_t     *sw;
46         snd_pcm_uframes_t        frag_size;
47
48         snd_pcm_hw_params_alloca (&hw);
49         snd_pcm_sw_params_alloca (&sw);
50
51 // COMMANDLINEOPTION: Linux ALSA Sound: -sndpcm <devicename> selects which pcm device to us, default is "default"
52         if ((i=COM_CheckParm("-sndpcm"))!=0)
53                 pcmname=com_argv[i+1];
54         if (!pcmname)
55                 pcmname = "default";
56
57 // COMMANDLINEOPTION: Linux ALSA Sound: -sndbits <number> sets sound precision to 8 or 16 bit (email me if you want others added)
58         if ((i=COM_CheckParm("-sndbits")) != 0)
59         {
60                 bps = atoi(com_argv[i+1]);
61                 if (bps != 16 && bps != 8)
62                 {
63                         Con_Printf("Error: invalid sample bits: %d\n", bps);
64                         return false;
65                 }
66         }
67
68 // COMMANDLINEOPTION: Linux ALSA Sound: -sndspeed <hz> chooses 44100 hz, 22100 hz, or 11025 hz sound output rate
69         if ((i=COM_CheckParm("-sndspeed")) != 0)
70         {
71                 rate = atoi(com_argv[i+1]);
72                 if (rate!=44100 && rate!=22050 && rate!=11025)
73                 {
74                         Con_Printf("Error: invalid sample rate: %d\n", rate);
75                         return false;
76                 }
77         }
78
79 // COMMANDLINEOPTION: Linux ALSA Sound: -sndmono sets sound output to mono
80         if ((i=COM_CheckParm("-sndmono")) != 0)
81                 stereo=0;
82 // COMMANDLINEOPTION: Linux ALSA Sound: -sndstereo sets sound output to stereo
83         if ((i=COM_CheckParm("-sndstereo")) != 0)
84                 stereo=1;
85
86         err = snd_pcm_open (&pcm, pcmname, SND_PCM_STREAM_PLAYBACK,
87                                                   SND_PCM_NONBLOCK);
88         if (0 > err) {
89                 Sys_Printf ("Error: audio open error: %s\n", snd_strerror (err));
90                 return 0;
91         }
92         Sys_Printf ("ALSA: Using PCM %s.\n", pcmname);
93
94         err = snd_pcm_hw_params_any (pcm, hw);
95         if (0 > err) {
96                 Sys_Printf ("ALSA: error setting hw_params_any. %s\n",
97                                         snd_strerror (err));
98                 goto error;
99         }
100
101         err = snd_pcm_hw_params_set_access (pcm, hw,
102                                                                                   SND_PCM_ACCESS_MMAP_INTERLEAVED);
103         if (0 > err) {
104                 Sys_Printf ("ALSA: Failure to set noninterleaved PCM access. %s\n"
105                                         "Note: Interleaved is not supported\n",
106                                         snd_strerror (err));
107                 goto error;
108         }
109
110         switch (bps) {
111                 case -1:
112                         err = snd_pcm_hw_params_set_format (pcm, hw,
113                                                                                                   SND_PCM_FORMAT_S16);
114                         if (0 <= err) {
115                                 bps = 16;
116                         } else if (0 <= (err = snd_pcm_hw_params_set_format (pcm, hw,
117                                                                                                                  SND_PCM_FORMAT_U8))) {
118                                 bps = 8;
119                         } else {
120                                 Sys_Printf ("ALSA: no useable formats. %s\n",
121                                                         snd_strerror (err));
122                                 goto error;
123                         }
124                         break;
125                 case 8:
126                 case 16:
127                         err = snd_pcm_hw_params_set_format (pcm, hw, bps == 8 ?
128                                                                                                   SND_PCM_FORMAT_U8 :
129                                                                                                   SND_PCM_FORMAT_S16);
130                         if (0 > err) {
131                                 Sys_Printf ("ALSA: no usable formats. %s\n",
132                                                         snd_strerror (err));
133                                 goto error;
134                         }
135                         break;
136                 default:
137                         Sys_Printf ("ALSA: desired format not supported\n");
138                         goto error;
139         }
140
141         switch (stereo) {
142                 case -1:
143                         err = snd_pcm_hw_params_set_channels (pcm, hw, 2);
144                         if (0 <= err) {
145                                 stereo = 1;
146                         } else if (0 <= (err = snd_pcm_hw_params_set_channels (pcm, hw,
147                                                                                                                                          1))) {
148                                 stereo = 0;
149                         } else {
150                                 Sys_Printf ("ALSA: no usable channels. %s\n",
151                                                         snd_strerror (err));
152                                 goto error;
153                         }
154                         break;
155                 case 0:
156                 case 1:
157                         err = snd_pcm_hw_params_set_channels (pcm, hw, stereo ? 2 : 1);
158                         if (0 > err) {
159                                 Sys_Printf ("ALSA: no usable channels. %s\n",
160                                                         snd_strerror (err));
161                                 goto error;
162                         }
163                         break;
164                 default:
165                         Sys_Printf ("ALSA: desired channels not supported\n");
166                         goto error;
167         }
168
169         switch (rate) {
170                 case 0:
171                         rate = 44100;
172                         err = snd_pcm_hw_params_set_rate_near (pcm, hw, &rate, 0);
173                         if (0 <= err) {
174                                 frag_size = 32 * bps;
175                         } else {
176                                 rate = 22050;
177                                 err = snd_pcm_hw_params_set_rate_near (pcm, hw, &rate, 0);
178                                 if (0 <= err) {
179                                         frag_size = 16 * bps;
180                                 } else {
181                                         rate = 11025;
182                                         err = snd_pcm_hw_params_set_rate_near (pcm, hw, &rate,
183                                                                                                                          0);
184                                         if (0 <= err) {
185                                                 frag_size = 8 * bps;
186                                         } else {
187                                                 Sys_Printf ("ALSA: no usable rates. %s\n",
188                                                                         snd_strerror (err));
189                                                 goto error;
190                                         }
191                                 }
192                         }
193                         break;
194                 case 11025:
195                 case 22050:
196                 case 44100:
197                         err = snd_pcm_hw_params_set_rate_near (pcm, hw, &rate, 0);
198                         if (0 > err) {
199                                 Sys_Printf ("ALSA: desired rate %i not supported. %s\n", rate,
200                                                         snd_strerror (err));
201                                 goto error;
202                         }
203                         frag_size = 8 * bps * rate / 11025;
204                         break;
205                 default:
206                         Sys_Printf ("ALSA: desired rate %i not supported.\n", rate);
207                         goto error;
208         }
209
210         err = snd_pcm_hw_params_set_period_size_near (pcm, hw, &frag_size, 0);
211         if (0 > err) {
212                 Sys_Printf ("ALSA: unable to set period size near %i. %s\n",
213                                         (int) frag_size, snd_strerror (err));
214                 goto error;
215         }
216         err = snd_pcm_hw_params (pcm, hw);
217         if (0 > err) {
218                 Sys_Printf ("ALSA: unable to install hw params: %s\n",
219                                         snd_strerror (err));
220                 goto error;
221         }
222         err = snd_pcm_sw_params_current (pcm, sw);
223         if (0 > err) {
224                 Sys_Printf ("ALSA: unable to determine current sw params. %s\n",
225                                         snd_strerror (err));
226                 goto error;
227         }
228         err = snd_pcm_sw_params_set_start_threshold (pcm, sw, ~0U);
229         if (0 > err) {
230                 Sys_Printf ("ALSA: unable to set playback threshold. %s\n",
231                                         snd_strerror (err));
232                 goto error;
233         }
234         err = snd_pcm_sw_params_set_stop_threshold (pcm, sw, ~0U);
235         if (0 > err) {
236                 Sys_Printf ("ALSA: unable to set playback stop threshold. %s\n",
237                                         snd_strerror (err));
238                 goto error;
239         }
240         err = snd_pcm_sw_params (pcm, sw);
241         if (0 > err) {
242                 Sys_Printf ("ALSA: unable to install sw params. %s\n",
243                                         snd_strerror (err));
244                 goto error;
245         }
246
247         shm->format.channels = stereo + 1;
248         shm->samplepos = 0;
249         shm->format.width = bps / 8;
250
251         err = snd_pcm_hw_params_get_buffer_size (hw, &buffer_size);
252         if (0 > err) {
253                 Sys_Printf ("ALSA: unable to get buffer size. %s\n",
254                                         snd_strerror (err));
255                 goto error;
256         }
257
258         shm->samples = buffer_size * shm->format.channels;              // mono samples in buffer
259         shm->format.speed = rate;
260         SNDDMA_GetDMAPos ();            // sets shm->buffer
261
262         snd_inited = 1;
263         return true;
264
265 error:
266         snd_pcm_close (pcm);
267         return false;
268 }
269
270 int SNDDMA_GetDMAPos (void)
271 {
272         const snd_pcm_channel_area_t *areas;
273         snd_pcm_uframes_t offset;
274         snd_pcm_uframes_t nframes = shm->samples/shm->format.channels;
275
276         if (!snd_inited)
277                 return 0;
278
279         snd_pcm_avail_update (pcm);
280         snd_pcm_mmap_begin (pcm, &areas, &offset, &nframes);
281         offset *= shm->format.channels;
282         nframes *= shm->format.channels;
283         shm->samplepos = offset;
284         shm->buffer = areas->addr;
285         return shm->samplepos;
286 }
287
288 void SNDDMA_Shutdown (void)
289 {
290         if (snd_inited) {
291                 snd_pcm_close (pcm);
292                 snd_inited = 0;
293         }
294 }
295
296 /*
297         SNDDMA_Submit
298
299         Send sound to device if buffer isn't really the dma buffer
300 */
301 void SNDDMA_Submit (void)
302 {
303         int                     state;
304         int                     count = paintedtime - soundtime;
305         const snd_pcm_channel_area_t *areas;
306         snd_pcm_uframes_t nframes;
307         snd_pcm_uframes_t offset;
308
309         nframes = count / shm->format.channels;
310
311         snd_pcm_avail_update (pcm);
312         snd_pcm_mmap_begin (pcm, &areas, &offset, &nframes);
313
314         state = snd_pcm_state (pcm);
315
316         switch (state) {
317                 case SND_PCM_STATE_PREPARED:
318                         snd_pcm_mmap_commit (pcm, offset, nframes);
319                         snd_pcm_start (pcm);
320                         break;
321                 case SND_PCM_STATE_RUNNING:
322                         snd_pcm_mmap_commit (pcm, offset, nframes);
323                         break;
324                 default:
325                         break;
326         }
327 }
328
329 void *S_LockBuffer(void)
330 {
331         return shm->buffer;
332 }
333
334 void S_UnlockBuffer(void)
335 {
336 }