Added snd_channellayout to configure the speaker layout dynamically (0: auto, 1:...
[divverent/darkplaces.git] / snd_oss.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
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 the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19 */
20
21 // OSS module, used by Linux and FreeBSD
22
23
24 #include <fcntl.h>
25 #include <sys/ioctl.h>
26 #include <sys/soundcard.h>
27 #include <unistd.h>
28
29 #include "quakedef.h"
30 #include "snd_main.h"
31
32
33 #define NB_FRAGMENTS 4
34
35 static int audio_fd = -1;
36 static int old_osstime = 0;
37 static unsigned int osssoundtime;
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         int flags, ioctl_param, prev_value;
51         unsigned int fragmentsize;
52
53         Con_DPrint("SndSys_Init: using the OSS module\n");
54
55         // Check the requested sound format
56         if (requested->width < 1 || requested->width > 2)
57         {
58                 Con_Printf("SndSys_Init: invalid sound width (%hu)\n",
59                                         requested->width);
60
61                 if (suggested != NULL)
62                 {
63                         memcpy(suggested, requested, sizeof(suggested));
64
65                         if (requested->width < 1)
66                                 suggested->width = 1;
67                         else
68                                 suggested->width = 2;
69                 }
70                 
71                 return false;
72     }
73
74         // Open /dev/dsp
75     audio_fd = open("/dev/dsp", O_WRONLY);
76         if (audio_fd < 0)
77         {
78                 perror("/dev/dsp");
79                 Con_Print("SndSys_Init: could not open /dev/dsp\n");
80                 return false;
81         }
82         
83         // Use non-blocking IOs if possible
84         flags = fcntl(audio_fd, F_GETFL);
85         if (flags != -1)
86         {
87                 if (fcntl(audio_fd, F_SETFL, flags | O_NONBLOCK) == -1)
88                         Con_Print("SndSys_Init : fcntl(F_SETFL, O_NONBLOCK) failed!\n");
89         }
90         else
91                 Con_Print("SndSys_Init: fcntl(F_GETFL) failed!\n");
92         
93         ioctl(audio_fd, SNDCTL_DSP_RESET, NULL);
94
95         // Set the fragment size (up to "NB_FRAGMENTS" fragments of "fragmentsize" bytes)
96         fragmentsize = requested->speed * requested->channels * requested->width / 5;
97         fragmentsize = (unsigned int)ceilf((float)fragmentsize / (float)NB_FRAGMENTS);
98         fragmentsize = CeilPowerOf2(fragmentsize);
99         ioctl_param = (NB_FRAGMENTS << 16) | log2i(fragmentsize);
100         if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &ioctl_param) == -1)
101         {
102                 Con_Print ("SndSys_Init: could not set the fragment size\n");
103                 SndSys_Shutdown ();
104                 return false;
105         }
106
107         // Set the sound width
108         if (requested->width == 1)
109                 ioctl_param = AFMT_U8;
110         else
111                 ioctl_param = AFMT_S16_NE;
112         prev_value = ioctl_param;
113         if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &ioctl_param) == -1 ||
114                 ioctl_param != prev_value)
115         {
116                 if (ioctl_param != prev_value && suggested != NULL)
117                 {
118                         memcpy(suggested, requested, sizeof(suggested));
119
120                         if (ioctl_param == AFMT_S16_NE)
121                                 suggested->width = 2;
122                         else
123                                 suggested->width = 1;
124                 }
125
126                 Con_Printf("SndSys_Init: could not set the sound width to %hu\n",
127                                         requested->width);
128                 SndSys_Shutdown();
129                 return false;
130         }
131
132         // Set the sound channels
133         ioctl_param = requested->channels;
134         if (ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &ioctl_param) == -1 ||
135                 ioctl_param != requested->channels)
136         {
137                 if (ioctl_param != requested->channels && suggested != NULL)
138                 {
139                         memcpy(suggested, requested, sizeof(suggested));
140                         suggested->channels = ioctl_param;
141                 }
142
143                 Con_Printf("SndSys_Init: could not set the number of channels to %hu\n",
144                                         requested->channels);
145                 SndSys_Shutdown();
146                 return false;
147         }
148
149         // Set the sound speed
150         ioctl_param = requested->speed;
151         if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &ioctl_param) == -1 ||
152                 (unsigned int)ioctl_param != requested->speed)
153         {
154                 if ((unsigned int)ioctl_param != requested->speed && suggested != NULL)
155                 {
156                         memcpy(suggested, requested, sizeof(suggested));
157                         suggested->speed = ioctl_param;
158                 }
159
160                 Con_Printf("SndSys_Init: could not set the sound speed to %u\n",
161                                         requested->speed);
162                 SndSys_Shutdown();
163                 return false;
164         }
165
166         // TOCHECK: I'm not sure which channel layout OSS uses for 5.1 and 7.1
167         if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO)
168                 Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_ALSA);
169
170         old_osstime = 0;
171         osssoundtime = 0;
172         snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL);
173         return true;
174 }
175
176
177 /*
178 ====================
179 SndSys_Shutdown
180
181 Stop the sound card, delete "snd_renderbuffer" and free its other resources
182 ====================
183 */
184 void SndSys_Shutdown (void)
185 {
186         // Stop the sound and close the device
187         if (audio_fd >= 0)
188         {
189                 ioctl(audio_fd, SNDCTL_DSP_RESET, NULL);
190                 close(audio_fd);
191                 audio_fd = -1;
192         }
193
194         if (snd_renderbuffer != NULL)
195         {
196                 Mem_Free(snd_renderbuffer->ring);
197                 Mem_Free(snd_renderbuffer);
198                 snd_renderbuffer = NULL;
199         }
200 }
201
202
203 /*
204 ====================
205 SndSys_Submit
206
207 Submit the contents of "snd_renderbuffer" to the sound card
208 ====================
209 */
210 void SndSys_Submit (void)
211 {
212         unsigned int startoffset, factor, limit, nbframes;
213         int written;
214         
215         if (audio_fd < 0 ||
216                 snd_renderbuffer->startframe == snd_renderbuffer->endframe)
217                 return;
218
219         startoffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes;
220         factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels;
221         limit = snd_renderbuffer->maxframes - startoffset;
222         nbframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe;
223         if (nbframes > limit)
224         {
225                 written = write (audio_fd, &snd_renderbuffer->ring[startoffset * factor], limit * factor);
226                 if (written < 0)
227                 {
228                         Con_Printf("SndSys_Submit: audio write returned %d!\n", written);
229                         return;
230                 }
231
232                 if (written % factor != 0)
233                         Sys_Error("SndSys_Submit: nb of bytes written (%d) isn't aligned to a frame sample!\n", written);
234
235                 snd_renderbuffer->startframe += written / factor;
236
237                 if ((unsigned int)written < limit * factor)
238                 {
239                         Con_Printf("SndSys_Submit: audio can't keep up! (%u < %u)\n", written, limit * factor);
240                         return;
241                 }
242                 
243                 nbframes -= limit;
244                 startoffset = 0;
245         }
246
247         written = write (audio_fd, &snd_renderbuffer->ring[startoffset * factor], nbframes * factor);
248         if (written < 0)
249         {
250                 Con_Printf("SndSys_Submit: audio write returned %d!\n", written);
251                 return;
252         }
253
254         if (written % factor != 0)
255                 Sys_Error("SndSys_Submit: nb of bytes written (%d) isn't aligned to a frame sample!\n", written);
256
257         snd_renderbuffer->startframe += written / factor;
258
259         if ((unsigned int)written < nbframes * factor)
260                 Con_Printf("SndSys_Submit: audio can't keep up! (%u < %u)\n", written, nbframes * factor);
261 }
262
263
264 /*
265 ====================
266 SndSys_GetSoundTime
267
268 Returns the number of sample frames consumed since the sound started
269 ====================
270 */
271 unsigned int SndSys_GetSoundTime (void)
272 {
273         struct count_info count;
274         int new_osstime;
275         unsigned int timediff;
276
277         // TODO: use SNDCTL_DSP_GETODELAY instead
278         if (ioctl (audio_fd, SNDCTL_DSP_GETOPTR, &count) == -1)
279         {
280                 Con_Print ("SndSys_GetSoundTimeDiff: can't ioctl (SNDCTL_DSP_GETOPTR)\n");
281                 return 0;
282         }
283         new_osstime = count.bytes / (snd_renderbuffer->format.width * snd_renderbuffer->format.channels);
284
285         if (new_osstime >= old_osstime)
286                 timediff = new_osstime - old_osstime;
287         else
288         {
289                 Con_Print ("SndSys_GetSoundTime: osstime wrapped\n");
290                 timediff = 0;
291         }
292
293         old_osstime = new_osstime;
294         osssoundtime += timediff;
295         return osssoundtime;
296 }
297
298
299 /*
300 ====================
301 SndSys_LockRenderBuffer
302
303 Get the exclusive lock on "snd_renderbuffer"
304 ====================
305 */
306 qboolean SndSys_LockRenderBuffer (void)
307 {
308         // Nothing to do
309         return true;
310 }
311
312
313 /*
314 ====================
315 SndSys_UnlockRenderBuffer
316
317 Release the exclusive lock on "snd_renderbuffer"
318 ====================
319 */
320 void SndSys_UnlockRenderBuffer (void)
321 {
322         // Nothing to do
323 }