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