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 #include "../client/client.h"
35
36 qboolean snd_inited = qfalse;
37
38 cvar_t *s_sdlBits;
39 cvar_t *s_sdlSpeed;
40 cvar_t *s_sdlChannels;
41 cvar_t *s_sdlDevSamps;
42 cvar_t *s_sdlMixSamps;
43
44 /* The audio callback. All the magic happens here. */
45 static int dmapos = 0;
46 static int dmasize = 0;
47
48 static SDL_AudioDeviceID sdlPlaybackDevice;
49
50 #if defined USE_VOIP && SDL_VERSION_ATLEAST( 2, 0, 5 )
51 #define USE_SDL_AUDIO_CAPTURE
52
53 static SDL_AudioDeviceID sdlCaptureDevice;
54 static cvar_t *s_sdlCapture;
55 static float sdlMasterGain = 1.0f;
56 #endif
57
58
59 /*
60 ===============
61 SNDDMA_AudioCallback
62 ===============
63 */
SNDDMA_AudioCallback(void * userdata,Uint8 * stream,int len)64 static void SNDDMA_AudioCallback(void *userdata, Uint8 *stream, int len)
65 {
66 int pos = (dmapos * (dma.samplebits/8));
67 if (pos >= dmasize)
68 dmapos = pos = 0;
69
70 if (!snd_inited) /* shouldn't happen, but just in case... */
71 {
72 memset(stream, '\0', len);
73 return;
74 }
75 else
76 {
77 int tobufend = dmasize - pos; /* bytes to buffer's end. */
78 int len1 = len;
79 int len2 = 0;
80
81 if (len1 > tobufend)
82 {
83 len1 = tobufend;
84 len2 = len - len1;
85 }
86 memcpy(stream, dma.buffer + pos, len1);
87 if (len2 <= 0)
88 dmapos += (len1 / (dma.samplebits/8));
89 else /* wraparound? */
90 {
91 memcpy(stream+len1, dma.buffer, len2);
92 dmapos = (len2 / (dma.samplebits/8));
93 }
94 }
95
96 if (dmapos >= dmasize)
97 dmapos = 0;
98
99 #ifdef USE_SDL_AUDIO_CAPTURE
100 if (sdlMasterGain != 1.0f)
101 {
102 int i;
103 if (dma.isfloat && (dma.samplebits == 32))
104 {
105 float *ptr = (float *) stream;
106 len /= sizeof (*ptr);
107 for (i = 0; i < len; i++, ptr++)
108 {
109 *ptr *= sdlMasterGain;
110 }
111 }
112 else if (dma.samplebits == 16)
113 {
114 Sint16 *ptr = (Sint16 *) stream;
115 len /= sizeof (*ptr);
116 for (i = 0; i < len; i++, ptr++)
117 {
118 *ptr = (Sint16) (((float) *ptr) * sdlMasterGain);
119 }
120 }
121 else if (dma.samplebits == 8)
122 {
123 Uint8 *ptr = (Uint8 *) stream;
124 len /= sizeof (*ptr);
125 for (i = 0; i < len; i++, ptr++)
126 {
127 *ptr = (Uint8) (((float) *ptr) * sdlMasterGain);
128 }
129 }
130 }
131 #endif
132 }
133
134 static struct
135 {
136 Uint16 enumFormat;
137 char *stringFormat;
138 } formatToStringTable[ ] =
139 {
140 { AUDIO_U8, "AUDIO_U8" },
141 { AUDIO_S8, "AUDIO_S8" },
142 { AUDIO_U16LSB, "AUDIO_U16LSB" },
143 { AUDIO_S16LSB, "AUDIO_S16LSB" },
144 { AUDIO_U16MSB, "AUDIO_U16MSB" },
145 { AUDIO_S16MSB, "AUDIO_S16MSB" },
146 { AUDIO_F32LSB, "AUDIO_F32LSB" },
147 { AUDIO_F32MSB, "AUDIO_F32MSB" }
148 };
149
150 static int formatToStringTableSize = ARRAY_LEN( formatToStringTable );
151
152 /*
153 ===============
154 SNDDMA_PrintAudiospec
155 ===============
156 */
SNDDMA_PrintAudiospec(const char * str,const SDL_AudioSpec * spec)157 static void SNDDMA_PrintAudiospec(const char *str, const SDL_AudioSpec *spec)
158 {
159 int i;
160 char *fmt = NULL;
161
162 Com_Printf("%s:\n", str);
163
164 for( i = 0; i < formatToStringTableSize; i++ ) {
165 if( spec->format == formatToStringTable[ i ].enumFormat ) {
166 fmt = formatToStringTable[ i ].stringFormat;
167 }
168 }
169
170 if( fmt ) {
171 Com_Printf( " Format: %s\n", fmt );
172 } else {
173 Com_Printf( " Format: " S_COLOR_RED "UNKNOWN\n");
174 }
175
176 Com_Printf( " Freq: %d\n", (int) spec->freq );
177 Com_Printf( " Samples: %d\n", (int) spec->samples );
178 Com_Printf( " Channels: %d\n", (int) spec->channels );
179 }
180
181 /*
182 ===============
183 SNDDMA_Init
184 ===============
185 */
SNDDMA_Init(void)186 qboolean SNDDMA_Init(void)
187 {
188 SDL_AudioSpec desired;
189 SDL_AudioSpec obtained;
190 int tmp;
191
192 if (snd_inited)
193 return qtrue;
194
195 if (!s_sdlBits) {
196 s_sdlBits = Cvar_Get("s_sdlBits", "16", CVAR_ARCHIVE);
197 s_sdlSpeed = Cvar_Get("s_sdlSpeed", "0", CVAR_ARCHIVE);
198 s_sdlChannels = Cvar_Get("s_sdlChannels", "2", CVAR_ARCHIVE);
199 s_sdlDevSamps = Cvar_Get("s_sdlDevSamps", "0", CVAR_ARCHIVE);
200 s_sdlMixSamps = Cvar_Get("s_sdlMixSamps", "0", CVAR_ARCHIVE);
201 }
202
203 Com_Printf( "SDL_Init( SDL_INIT_AUDIO )... " );
204
205 if (SDL_Init(SDL_INIT_AUDIO) != 0)
206 {
207 Com_Printf( "FAILED (%s)\n", SDL_GetError( ) );
208 return qfalse;
209 }
210
211 Com_Printf( "OK\n" );
212
213 Com_Printf( "SDL audio driver is \"%s\".\n", SDL_GetCurrentAudioDriver( ) );
214
215 memset(&desired, '\0', sizeof (desired));
216 memset(&obtained, '\0', sizeof (obtained));
217
218 tmp = ((int) s_sdlBits->value);
219 if ((tmp != 16) && (tmp != 8))
220 tmp = 16;
221
222 desired.freq = (int) s_sdlSpeed->value;
223 if(!desired.freq) desired.freq = 22050;
224 desired.format = ((tmp == 16) ? AUDIO_S16SYS : AUDIO_U8);
225
226 // I dunno if this is the best idea, but I'll give it a try...
227 // should probably check a cvar for this...
228 if (s_sdlDevSamps->value)
229 desired.samples = s_sdlDevSamps->value;
230 else
231 {
232 // just pick a sane default.
233 if (desired.freq <= 11025)
234 desired.samples = 256;
235 else if (desired.freq <= 22050)
236 desired.samples = 512;
237 else if (desired.freq <= 44100)
238 desired.samples = 1024;
239 else
240 desired.samples = 2048; // (*shrug*)
241 }
242
243 desired.channels = (int) s_sdlChannels->value;
244 desired.callback = SNDDMA_AudioCallback;
245
246 sdlPlaybackDevice = SDL_OpenAudioDevice(NULL, SDL_FALSE, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE);
247 if (sdlPlaybackDevice == 0)
248 {
249 Com_Printf("SDL_OpenAudioDevice() failed: %s\n", SDL_GetError());
250 SDL_QuitSubSystem(SDL_INIT_AUDIO);
251 return qfalse;
252 }
253
254 SNDDMA_PrintAudiospec("SDL_AudioSpec", &obtained);
255
256 // dma.samples needs to be big, or id's mixer will just refuse to
257 // work at all; we need to keep it significantly bigger than the
258 // amount of SDL callback samples, and just copy a little each time
259 // the callback runs.
260 // 32768 is what the OSS driver filled in here on my system. I don't
261 // know if it's a good value overall, but at least we know it's
262 // reasonable...this is why I let the user override.
263 tmp = s_sdlMixSamps->value;
264 if (!tmp)
265 tmp = (obtained.samples * obtained.channels) * 10;
266
267 // samples must be divisible by number of channels
268 tmp -= tmp % obtained.channels;
269
270 dmapos = 0;
271 dma.samplebits = SDL_AUDIO_BITSIZE(obtained.format);
272 dma.isfloat = SDL_AUDIO_ISFLOAT(obtained.format);
273 dma.channels = obtained.channels;
274 dma.samples = tmp;
275 dma.fullsamples = dma.samples / dma.channels;
276 dma.submission_chunk = 1;
277 dma.speed = obtained.freq;
278 dmasize = (dma.samples * (dma.samplebits/8));
279 dma.buffer = calloc(1, dmasize);
280
281 #ifdef USE_SDL_AUDIO_CAPTURE
282 // !!! FIXME: some of these SDL_OpenAudioDevice() values should be cvars.
283 s_sdlCapture = Cvar_Get( "s_sdlCapture", "1", CVAR_ARCHIVE | CVAR_LATCH );
284 // !!! FIXME: pulseaudio capture records audio the entire time the program is running. https://bugzilla.libsdl.org/show_bug.cgi?id=4087
285 if (Q_stricmp(SDL_GetCurrentAudioDriver(), "pulseaudio") == 0)
286 {
287 Com_Printf("SDL audio capture support disabled for pulseaudio (https://bugzilla.libsdl.org/show_bug.cgi?id=4087)\n");
288 }
289 else if (!s_sdlCapture->integer)
290 {
291 Com_Printf("SDL audio capture support disabled by user ('+set s_sdlCapture 1' to enable)\n");
292 }
293 #if USE_MUMBLE
294 else if (cl_useMumble->integer)
295 {
296 Com_Printf("SDL audio capture support disabled for Mumble support\n");
297 }
298 #endif
299 else
300 {
301 /* !!! FIXME: list available devices and let cvar specify one, like OpenAL does */
302 SDL_AudioSpec spec;
303 SDL_zero(spec);
304 spec.freq = 48000;
305 spec.format = AUDIO_S16SYS;
306 spec.channels = 1;
307 spec.samples = VOIP_MAX_PACKET_SAMPLES * 4;
308 sdlCaptureDevice = SDL_OpenAudioDevice(NULL, SDL_TRUE, &spec, NULL, 0);
309 Com_Printf( "SDL capture device %s.\n",
310 (sdlCaptureDevice == 0) ? "failed to open" : "opened");
311 }
312
313 sdlMasterGain = 1.0f;
314 #endif
315
316 Com_Printf("Starting SDL audio callback...\n");
317 SDL_PauseAudioDevice(sdlPlaybackDevice, 0); // start callback.
318 // don't unpause the capture device; we'll do that in StartCapture.
319
320 Com_Printf("SDL audio initialized.\n");
321 snd_inited = qtrue;
322 return qtrue;
323 }
324
325 /*
326 ===============
327 SNDDMA_GetDMAPos
328 ===============
329 */
SNDDMA_GetDMAPos(void)330 int SNDDMA_GetDMAPos(void)
331 {
332 return dmapos;
333 }
334
335 /*
336 ===============
337 SNDDMA_Shutdown
338 ===============
339 */
SNDDMA_Shutdown(void)340 void SNDDMA_Shutdown(void)
341 {
342 if (sdlPlaybackDevice != 0)
343 {
344 Com_Printf("Closing SDL audio playback device...\n");
345 SDL_CloseAudioDevice(sdlPlaybackDevice);
346 Com_Printf("SDL audio playback device closed.\n");
347 sdlPlaybackDevice = 0;
348 }
349
350 #ifdef USE_SDL_AUDIO_CAPTURE
351 if (sdlCaptureDevice)
352 {
353 Com_Printf("Closing SDL audio capture device...\n");
354 SDL_CloseAudioDevice(sdlCaptureDevice);
355 Com_Printf("SDL audio capture device closed.\n");
356 sdlCaptureDevice = 0;
357 }
358 #endif
359
360 SDL_QuitSubSystem(SDL_INIT_AUDIO);
361 free(dma.buffer);
362 dma.buffer = NULL;
363 dmapos = dmasize = 0;
364 snd_inited = qfalse;
365 Com_Printf("SDL audio shut down.\n");
366 }
367
368 /*
369 ===============
370 SNDDMA_Submit
371
372 Send sound to device if buffer isn't really the dma buffer
373 ===============
374 */
SNDDMA_Submit(void)375 void SNDDMA_Submit(void)
376 {
377 SDL_UnlockAudioDevice(sdlPlaybackDevice);
378 }
379
380 /*
381 ===============
382 SNDDMA_BeginPainting
383 ===============
384 */
SNDDMA_BeginPainting(void)385 void SNDDMA_BeginPainting (void)
386 {
387 SDL_LockAudioDevice(sdlPlaybackDevice);
388 }
389
390
391 #ifdef USE_VOIP
SNDDMA_StartCapture(void)392 void SNDDMA_StartCapture(void)
393 {
394 #ifdef USE_SDL_AUDIO_CAPTURE
395 if (sdlCaptureDevice)
396 {
397 SDL_ClearQueuedAudio(sdlCaptureDevice);
398 SDL_PauseAudioDevice(sdlCaptureDevice, 0);
399 }
400 #endif
401 }
402
SNDDMA_AvailableCaptureSamples(void)403 int SNDDMA_AvailableCaptureSamples(void)
404 {
405 #ifdef USE_SDL_AUDIO_CAPTURE
406 // divided by 2 to convert from bytes to (mono16) samples.
407 return sdlCaptureDevice ? (SDL_GetQueuedAudioSize(sdlCaptureDevice) / 2) : 0;
408 #else
409 return 0;
410 #endif
411 }
412
SNDDMA_Capture(int samples,byte * data)413 void SNDDMA_Capture(int samples, byte *data)
414 {
415 #ifdef USE_SDL_AUDIO_CAPTURE
416 // multiplied by 2 to convert from (mono16) samples to bytes.
417 if (sdlCaptureDevice)
418 {
419 SDL_DequeueAudio(sdlCaptureDevice, data, samples * 2);
420 }
421 else
422 #endif
423 {
424 SDL_memset(data, '\0', samples * 2);
425 }
426 }
427
SNDDMA_StopCapture(void)428 void SNDDMA_StopCapture(void)
429 {
430 #ifdef USE_SDL_AUDIO_CAPTURE
431 if (sdlCaptureDevice)
432 {
433 SDL_PauseAudioDevice(sdlCaptureDevice, 1);
434 }
435 #endif
436 }
437
SNDDMA_MasterGain(float val)438 void SNDDMA_MasterGain( float val )
439 {
440 #ifdef USE_SDL_AUDIO_CAPTURE
441 sdlMasterGain = val;
442 #endif
443 }
444 #endif
445
446