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 =
103 sizeof( formatToStringTable ) / sizeof( formatToStringTable[ 0 ] );
104
105 /*
106 ===============
107 SNDDMA_PrintAudiospec
108 ===============
109 */
SNDDMA_PrintAudiospec(const char * str,const SDL_AudioSpec * spec)110 static void SNDDMA_PrintAudiospec(const char *str, const SDL_AudioSpec *spec)
111 {
112 int i;
113 char *fmt = NULL;
114
115 Com_Printf("%s:\n", str);
116
117 for( i = 0; i < formatToStringTableSize; i++ ) {
118 if( spec->format == formatToStringTable[ i ].enumFormat ) {
119 fmt = formatToStringTable[ i ].stringFormat;
120 }
121 }
122
123 if( fmt ) {
124 Com_Printf( " Format: %s\n", fmt );
125 } else {
126 Com_Printf( " Format: " S_COLOR_RED "UNKNOWN\n");
127 }
128
129 Com_Printf( " Freq: %d\n", (int) spec->freq );
130 Com_Printf( " Samples: %d\n", (int) spec->samples );
131 Com_Printf( " Channels: %d\n", (int) spec->channels );
132 }
133
134 /*
135 ===============
136 SNDDMA_Init
137 ===============
138 */
SNDDMA_Init(void)139 qboolean SNDDMA_Init(void)
140 {
141 char drivername[128];
142 SDL_AudioSpec desired;
143 SDL_AudioSpec obtained;
144 int tmp;
145
146 if (snd_inited)
147 return qtrue;
148
149 if (!s_sdlBits) {
150 s_sdlBits = Cvar_Get("s_sdlBits", "16", CVAR_ARCHIVE);
151 s_sdlSpeed = Cvar_Get("s_sdlSpeed", "0", CVAR_ARCHIVE);
152 s_sdlChannels = Cvar_Get("s_sdlChannels", "2", CVAR_ARCHIVE);
153 s_sdlDevSamps = Cvar_Get("s_sdlDevSamps", "0", CVAR_ARCHIVE);
154 s_sdlMixSamps = Cvar_Get("s_sdlMixSamps", "0", CVAR_ARCHIVE);
155 }
156
157 Com_Printf( "SDL_Init( SDL_INIT_AUDIO )... " );
158
159 if (!SDL_WasInit(SDL_INIT_AUDIO))
160 {
161 if (SDL_Init(SDL_INIT_AUDIO) == -1)
162 {
163 Com_Printf( "FAILED (%s)\n", SDL_GetError( ) );
164 return qfalse;
165 }
166 }
167
168 Com_Printf( "OK\n" );
169
170 if (SDL_AudioDriverName(drivername, sizeof (drivername)) == NULL)
171 strcpy(drivername, "(UNKNOWN)");
172 Com_Printf("SDL audio driver is \"%s\".\n", drivername);
173
174 memset(&desired, '\0', sizeof (desired));
175 memset(&obtained, '\0', sizeof (obtained));
176
177 tmp = ((int) s_sdlBits->value);
178 if ((tmp != 16) && (tmp != 8))
179 tmp = 16;
180
181 desired.freq = (int) s_sdlSpeed->value;
182 if(!desired.freq) desired.freq = 22050;
183 desired.format = ((tmp == 16) ? AUDIO_S16SYS : AUDIO_U8);
184
185 // I dunno if this is the best idea, but I'll give it a try...
186 // should probably check a cvar for this...
187 if (s_sdlDevSamps->value)
188 desired.samples = s_sdlDevSamps->value;
189 else
190 {
191 // just pick a sane default.
192 if (desired.freq <= 11025)
193 desired.samples = 256;
194 else if (desired.freq <= 22050)
195 desired.samples = 512;
196 else if (desired.freq <= 44100)
197 desired.samples = 1024;
198 else
199 desired.samples = 2048; // (*shrug*)
200 }
201
202 desired.channels = (int) s_sdlChannels->value;
203 desired.callback = SNDDMA_AudioCallback;
204
205 if (SDL_OpenAudio(&desired, &obtained) == -1)
206 {
207 Com_Printf("SDL_OpenAudio() failed: %s\n", SDL_GetError());
208 SDL_QuitSubSystem(SDL_INIT_AUDIO);
209 return qfalse;
210 }
211
212 SNDDMA_PrintAudiospec("SDL_AudioSpec", &obtained);
213
214 // dma.samples needs to be big, or id's mixer will just refuse to
215 // work at all; we need to keep it significantly bigger than the
216 // amount of SDL callback samples, and just copy a little each time
217 // the callback runs.
218 // 32768 is what the OSS driver filled in here on my system. I don't
219 // know if it's a good value overall, but at least we know it's
220 // reasonable...this is why I let the user override.
221 tmp = s_sdlMixSamps->value;
222 if (!tmp)
223 tmp = (obtained.samples * obtained.channels) * 10;
224
225 if (tmp & (tmp - 1)) // not a power of two? Seems to confuse something.
226 {
227 int val = 1;
228 while (val < tmp)
229 val <<= 1;
230
231 tmp = val;
232 }
233
234 dmapos = 0;
235 dma.samplebits = obtained.format & 0xFF; // first byte of format is bits.
236 dma.channels = obtained.channels;
237 dma.samples = tmp;
238 dma.submission_chunk = 1;
239 dma.speed = obtained.freq;
240 dmasize = (dma.samples * (dma.samplebits/8));
241 dma.buffer = calloc(1, dmasize);
242
243 Com_Printf("Starting SDL audio callback...\n");
244 SDL_PauseAudio(0); // start callback.
245
246 Com_Printf("SDL audio initialized.\n");
247 snd_inited = qtrue;
248 return qtrue;
249 }
250
251 /*
252 ===============
253 SNDDMA_GetDMAPos
254 ===============
255 */
SNDDMA_GetDMAPos(void)256 int SNDDMA_GetDMAPos(void)
257 {
258 return dmapos;
259 }
260
261 /*
262 ===============
263 SNDDMA_Shutdown
264 ===============
265 */
SNDDMA_Shutdown(void)266 void SNDDMA_Shutdown(void)
267 {
268 Com_Printf("Closing SDL audio device...\n");
269 SDL_PauseAudio(1);
270 SDL_CloseAudio();
271 SDL_QuitSubSystem(SDL_INIT_AUDIO);
272 free(dma.buffer);
273 dma.buffer = NULL;
274 dmapos = dmasize = 0;
275 snd_inited = qfalse;
276 Com_Printf("SDL audio device shut down.\n");
277 }
278
279 /*
280 ===============
281 SNDDMA_Submit
282
283 Send sound to device if buffer isn't really the dma buffer
284 ===============
285 */
SNDDMA_Submit(void)286 void SNDDMA_Submit(void)
287 {
288 SDL_UnlockAudio();
289 }
290
291 /*
292 ===============
293 SNDDMA_BeginPainting
294 ===============
295 */
SNDDMA_BeginPainting(void)296 void SNDDMA_BeginPainting (void)
297 {
298 SDL_LockAudio();
299 }
300