Attempt to fix speaker layout for 5.1 and 7.1 sound, on Windows and Mac OS X; the...
[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         // Set the fragment size (up to "NB_FRAGMENTS" fragments of "fragmentsize" bytes)
94         fragmentsize = requested->speed * requested->channels * requested->width / 5;
95         fragmentsize = (unsigned int)ceilf((float)fragmentsize / (float)NB_FRAGMENTS);
96         fragmentsize = CeilPowerOf2(fragmentsize);
97         ioctl_param = (NB_FRAGMENTS << 16) | log2i(fragmentsize);
98         if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &ioctl_param) == -1)
99         {
100                 Con_Print ("SndSys_Init: could not set the fragment size\n");
101                 SndSys_Shutdown ();
102                 return false;
103         }
104
105         // Set the sound width
106         if (requested->width == 1)
107                 ioctl_param = AFMT_U8;
108         else
109                 ioctl_param = AFMT_S16_NE;
110         prev_value = ioctl_param;
111         if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &ioctl_param) == -1 ||
112                 ioctl_param != prev_value)
113         {
114                 if (ioctl_param != prev_value && suggested != NULL)
115                 {
116                         memcpy(suggested, requested, sizeof(suggested));
117
118                         if (ioctl_param == AFMT_S16_NE)
119                                 suggested->width = 2;
120                         else
121                                 suggested->width = 1;
122                 }
123
124                 Con_Printf("SndSys_Init: could not set the sound width to %hu\n",
125                                         requested->width);
126                 SndSys_Shutdown();
127                 return false;
128         }
129
130         // Set the sound channels
131         ioctl_param = requested->channels;
132         if (ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &ioctl_param) == -1 ||
133                 ioctl_param != requested->channels)
134         {
135                 if (ioctl_param != requested->channels && suggested != NULL)
136                 {
137                         memcpy(suggested, requested, sizeof(suggested));
138                         suggested->channels = ioctl_param;
139                 }
140
141                 Con_Printf("SndSys_Init: could not set the number of channels to %hu\n",
142                                         requested->channels);
143                 SndSys_Shutdown();
144                 return false;
145         }
146
147         // Set the sound speed
148         ioctl_param = requested->speed;
149         if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &ioctl_param) == -1 ||
150                 (unsigned int)ioctl_param != requested->speed)
151         {
152                 if ((unsigned int)ioctl_param != requested->speed && suggested != NULL)
153                 {
154                         memcpy(suggested, requested, sizeof(suggested));
155                         suggested->speed = ioctl_param;
156                 }
157
158                 Con_Printf("SndSys_Init: could not set the sound speed to %u\n",
159                                         requested->speed);
160                 SndSys_Shutdown();
161                 return false;
162         }
163         
164 #ifdef __linux__
165         alsaspeakerlayout = true;
166 #else
167         alsaspeakerlayout = false;
168 #endif
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, 0);
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 }