- the Linux sound modules (ALSA and OSS) are now write-based, instead of
[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         old_osstime = 0;
165         osssoundtime = 0;
166         snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL);
167         return true;
168 }
169
170
171 /*
172 ====================
173 SndSys_Shutdown
174
175 Stop the sound card, delete "snd_renderbuffer" and free its other resources
176 ====================
177 */
178 void SndSys_Shutdown (void)
179 {
180         // Stop the sound and close the device
181         if (audio_fd >= 0)
182         {
183                 ioctl(audio_fd, SNDCTL_DSP_RESET, 0);
184                 close(audio_fd);
185                 audio_fd = -1;
186         }
187
188         if (snd_renderbuffer != NULL)
189         {
190                 Mem_Free(snd_renderbuffer->ring);
191                 Mem_Free(snd_renderbuffer);
192                 snd_renderbuffer = NULL;
193         }
194 }
195
196
197 /*
198 ====================
199 SndSys_Submit
200
201 Submit the contents of "snd_renderbuffer" to the sound card
202 ====================
203 */
204 void SndSys_Submit (void)
205 {
206         unsigned int startoffset, factor, limit, nbframes;
207         int written;
208         
209         if (audio_fd < 0 ||
210                 snd_renderbuffer->startframe == snd_renderbuffer->endframe)
211                 return;
212
213         startoffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes;
214         factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels;
215         limit = snd_renderbuffer->maxframes - startoffset;
216         nbframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe;
217         if (nbframes > limit)
218         {
219                 written = write (audio_fd, &snd_renderbuffer->ring[startoffset * factor], limit * factor);
220                 if (written < 0)
221                 {
222                         Con_Printf("SndSys_Submit: audio write returned %d!\n", written);
223                         return;
224                 }
225
226                 if (written % factor != 0)
227                         Sys_Error("SndSys_Submit: nb of bytes written (%d) isn't aligned to a frame sample!\n", written);
228
229                 snd_renderbuffer->startframe += written / factor;
230
231                 if ((unsigned int)written < nbframes * factor)
232                 {
233                         Con_Printf("SndSys_Submit: audio can't keep up! (%d < %u)\n", written, nbframes * factor);
234                         return;
235                 }
236                 
237                 nbframes -= limit;
238                 startoffset = 0;
239         }
240
241         written = write (audio_fd, &snd_renderbuffer->ring[startoffset * factor], nbframes * factor);
242         if (written < 0)
243         {
244                 Con_Printf("SndSys_Submit: audio write returned %d!\n", written);
245                 return;
246         }
247         snd_renderbuffer->startframe += written / factor;
248 }
249
250
251 /*
252 ====================
253 SndSys_GetSoundTime
254
255 Returns the number of sample frames consumed since the sound started
256 ====================
257 */
258 unsigned int SndSys_GetSoundTime (void)
259 {
260         struct count_info count;
261         int new_osstime;
262         unsigned int timediff;
263
264         // TODO: use SNDCTL_DSP_GETODELAY instead
265         if (ioctl (audio_fd, SNDCTL_DSP_GETOPTR, &count) == -1)
266         {
267                 Con_Print ("SndSys_GetSoundTimeDiff: can't ioctl (SNDCTL_DSP_GETOPTR)\n");
268                 return 0;
269         }
270         new_osstime = count.bytes / (snd_renderbuffer->format.width * snd_renderbuffer->format.channels);
271
272         if (new_osstime >= old_osstime)
273                 timediff = new_osstime - old_osstime;
274         else
275         {
276                 Con_Print ("SndSys_GetSoundTime: osstime wrapped\n");
277                 timediff = 0;
278         }
279
280         old_osstime = new_osstime;
281         osssoundtime += timediff;
282         return osssoundtime;
283 }
284
285
286 /*
287 ====================
288 SndSys_LockRenderBuffer
289
290 Get the exclusive lock on "snd_renderbuffer"
291 ====================
292 */
293 qboolean SndSys_LockRenderBuffer (void)
294 {
295         // Nothing to do
296         return true;
297 }
298
299
300 /*
301 ====================
302 SndSys_UnlockRenderBuffer
303
304 Release the exclusive lock on "snd_renderbuffer"
305 ====================
306 */
307 void SndSys_UnlockRenderBuffer (void)
308 {
309         // Nothing to do
310 }