1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22
23 #include <stdlib.h>
24 #include <stdio.h>
25
26 #ifdef USE_LOCAL_HEADERS
27 # include "SDL.h"
28 #else
29 # include <SDL.h>
30 #endif
31
32 #include "../qcommon/q_shared.h"
33 #include "../client/snd_local.h"
34
35 qboolean snd_inited = qfalse;
36
37 cvar_t *s_sdlBits;
38 cvar_t *s_sdlSpeed;
39 cvar_t *s_sdlChannels;
40 cvar_t *s_sdlDevSamps;
41 cvar_t *s_sdlMixSamps;
42
43 /* The audio callback. All the magic happens here. */
44 static int dmapos = 0;
45 static int dmasize = 0;
46
47 /*
48 ===============
49 SNDDMA_AudioCallback
50 ===============
51 */
SNDDMA_AudioCallback(void * userdata,Uint8 * stream,int len)52 static void SNDDMA_AudioCallback(void *userdata, Uint8 *stream, int len)
53 {
54 int pos = (dmapos * (dma.samplebits/8));
55 if (pos >= dmasize)
56 dmapos = pos = 0;
57
58 if (!snd_inited) /* shouldn't happen, but just in case... */
59 {
60 memset(stream, '\0', len);
61 return;
62 }
63 else
64 {
65 int tobufend = dmasize - pos; /* bytes to buffer's end. */
66 int len1 = len;
67 int len2 = 0;
68
69 if (len1 > tobufend)
70 {
71 len1 = tobufend;
72 len2 = len - len1;
73 }
74 memcpy(stream, dma.buffer + pos, len1);
75 if (len2 <= 0)
76 dmapos += (len1 / (dma.samplebits/8));
77 else /* wraparound? */
78 {
79 memcpy(stream+len1, dma.buffer, len2);
80 dmapos = (len2 / (dma.samplebits/8));
81 }
82 }
83
84 if (dmapos >= dmasize)
85 dmapos = 0;
86 }
87
88 static struct
89 {
90 Uint16 enumFormat;
91 char *stringFormat;
92 } formatToStringTable[ ] =
93 {
94 { AUDIO_U8, "AUDIO_U8" },
95 { AUDIO_S8, "AUDIO_S8" },
96 { AUDIO_U16LSB, "AUDIO_U16LSB" },
97 { AUDIO_S16LSB, "AUDIO_S16LSB" },
98 { AUDIO_U16MSB, "AUDIO_U16MSB" },
99 { AUDIO_S16MSB, "AUDIO_S16MSB" }
100 };
101
102 static int formatToStringTableSize = ARRAY_LEN( formatToStringTable );
103
104 /*
105 ===============
106 SNDDMA_PrintAudiospec
107 ===============
108 */
SNDDMA_PrintAudiospec(const char * str,const SDL_AudioSpec * spec)109 static void SNDDMA_PrintAudiospec(const char *str, const SDL_AudioSpec *spec)
110 {
111 int i;
112 char *fmt = NULL;
113
114 Com_Printf("%s:\n", str);
115
116 for( i = 0; i < formatToStringTableSize; i++ ) {
117 if( spec->format == formatToStringTable[ i ].enumFormat ) {
118 fmt = formatToStringTable[ i ].stringFormat;
119 }
120 }
121
122 if( fmt ) {
123 Com_Printf( " Format: %s\n", fmt );
124 } else {
125 Com_Printf( " Format: " S_COLOR_RED "UNKNOWN\n");
126 }
127
128 Com_Printf( " Freq: %d\n", (int) spec->freq );
129 Com_Printf( " Samples: %d\n", (int) spec->samples );
130 Com_Printf( " Channels: %d\n", (int) spec->channels );
131 }
132
133 /*
134 ===============
135 SNDDMA_Init
136 ===============
137 */
SNDDMA_Init(void)138 qboolean SNDDMA_Init(void)
139 {
140 char drivername[128];
141 SDL_AudioSpec desired;
142 SDL_AudioSpec obtained;
143 int tmp;
144
145 if (snd_inited)
146 return qtrue;
147
148 if (!s_sdlBits) {
149 s_sdlBits = Cvar_Get("s_sdlBits", "16", CVAR_ARCHIVE);
150 s_sdlSpeed = Cvar_Get("s_sdlSpeed", "0", CVAR_ARCHIVE);
151 s_sdlChannels = Cvar_Get("s_sdlChannels", "2", CVAR_ARCHIVE);
152 s_sdlDevSamps = Cvar_Get("s_sdlDevSamps", "0", CVAR_ARCHIVE);
153 s_sdlMixSamps = Cvar_Get("s_sdlMixSamps", "0", CVAR_ARCHIVE);
154 }
155
156 Com_Printf( "SDL_Init( SDL_INIT_AUDIO )... " );
157
158 if (!SDL_WasInit(SDL_INIT_AUDIO))
159 {
160 if (SDL_Init(SDL_INIT_AUDIO) == -1)
161 {
162 Com_Printf( "FAILED (%s)\n", SDL_GetError( ) );
163 return qfalse;
164 }
165 }
166
167 Com_Printf( "OK\n" );
168
169 if (SDL_AudioDriverName(drivername, sizeof (drivername)) == NULL)
170 strcpy(drivername, "(UNKNOWN)");
171 Com_Printf("SDL audio driver is \"%s\".\n", drivername);
172
173 memset(&desired, '\0', sizeof (desired));
174 memset(&obtained, '\0', sizeof (obtained));
175
176 tmp = ((int) s_sdlBits->value);
177 if ((tmp != 16) && (tmp != 8))
178 tmp = 16;
179
180 desired.freq = (int) s_sdlSpeed->value;
181 if(!desired.freq) desired.freq = 22050;
182 desired.format = ((tmp == 16) ? AUDIO_S16SYS : AUDIO_U8);
183
184 // I dunno if this is the best idea, but I'll give it a try...
185 // should probably check a cvar for this...
186 if (s_sdlDevSamps->value)
187 desired.samples = s_sdlDevSamps->value;
188 else
189 {
190 // just pick a sane default.
191 if (desired.freq <= 11025)
192 desired.samples = 256;
193 else if (desired.freq <= 22050)
194 desired.samples = 512;
195 else if (desired.freq <= 44100)
196 desired.samples = 1024;
197 else
198 desired.samples = 2048; // (*shrug*)
199 }
200
201 desired.channels = (int) s_sdlChannels->value;
202 desired.callback = SNDDMA_AudioCallback;
203
204 if (SDL_OpenAudio(&desired, &obtained) == -1)
205 {
206 Com_Printf("SDL_OpenAudio() failed: %s\n", SDL_GetError());
207 SDL_QuitSubSystem(SDL_INIT_AUDIO);
208 return qfalse;
209 }
210
211 SNDDMA_PrintAudiospec("SDL_AudioSpec", &obtained);
212
213 // dma.samples needs to be big, or id's mixer will just refuse to
214 // work at all; we need to keep it significantly bigger than the
215 // amount of SDL callback samples, and just copy a little each time
216 // the callback runs.
217 // 32768 is what the OSS driver filled in here on my system. I don't
218 // know if it's a good value overall, but at least we know it's
219 // reasonable...this is why I let the user override.
220 tmp = s_sdlMixSamps->value;
221 if (!tmp)
222 tmp = (obtained.samples * obtained.channels) * 10;
223
224 if (tmp & (tmp - 1)) // not a power of two? Seems to confuse something.
225 {
226 int val = 1;
227 while (val < tmp)
228 val <<= 1;
229
230 tmp = val;
231 }
232
233 dmapos = 0;
234 dma.samplebits = obtained.format & 0xFF; // first byte of format is bits.
235 dma.channels = obtained.channels;
236 dma.samples = tmp;
237 dma.submission_chunk = 1;
238 dma.speed = obtained.freq;
239 dmasize = (dma.samples * (dma.samplebits/8));
240 dma.buffer = calloc(1, dmasize);
241
242 Com_Printf("Starting SDL audio callback...\n");
243 SDL_PauseAudio(0); // start callback.
244
245 Com_Printf("SDL audio initialized.\n");
246 snd_inited = qtrue;
247 return qtrue;
248 }
249
250 /*
251 ===============
252 SNDDMA_GetDMAPos
253 ===============
254 */
SNDDMA_GetDMAPos(void)255 int SNDDMA_GetDMAPos(void)
256 {
257 return dmapos;
258 }
259
260 /*
261 ===============
262 SNDDMA_Shutdown
263 ===============
264 */
SNDDMA_Shutdown(void)265 void SNDDMA_Shutdown(void)
266 {
267 Com_Printf("Closing SDL audio device...\n");
268 SDL_PauseAudio(1);
269 SDL_CloseAudio();
270 SDL_QuitSubSystem(SDL_INIT_AUDIO);
271 free(dma.buffer);
272 dma.buffer = NULL;
273 dmapos = dmasize = 0;
274 snd_inited = qfalse;
275 Com_Printf("SDL audio device shut down.\n");
276 }
277
278 /*
279 ===============
280 SNDDMA_Submit
281
282 Send sound to device if buffer isn't really the dma buffer
283 ===============
284 */
SNDDMA_Submit(void)285 void SNDDMA_Submit(void)
286 {
287 SDL_UnlockAudio();
288 }
289
290 /*
291 ===============
292 SNDDMA_BeginPainting
293 ===============
294 */
SNDDMA_BeginPainting(void)295 void SNDDMA_BeginPainting (void)
296 {
297 SDL_LockAudio();
298 }
299