4
* SDL implementation for Quake 3: Arena's GPL source release.
6
* This is a replacement of the Linux/OpenSoundSystem code with
7
* an SDL backend, since it allows us to trivially point just about any
8
* existing 2D audio backend known to man on any platform at the code,
9
* plus it benefits from all of SDL's tapdancing to support buggy drivers,
10
* etc, and gets us free ALSA support, too.
12
* This is the best idea for a direct modernization of the Linux sound code
13
* in Quake 3. However, it would be nice to replace this with true 3D
14
* positional audio, compliments of OpenAL...
16
* Written by Ryan C. Gordon (icculus@icculus.org). Please refer to
17
* http://icculus.org/quake3/ for the latest version of this code.
19
* Patches and comments are welcome at the above address.
21
* I cut-and-pasted this from linux_snd.c, and moved it to SDL line-by-line.
22
* There is probably some cruft that could be removed here.
24
* You should define USE_SDL=1 and then add this to the makefile.
25
* USE_SDL will disable the Open Sound System target.
29
Original copyright on Q3A sources:
30
===========================================================================
31
Copyright (C) 1999-2005 Id Software, Inc.
33
This file is part of Quake III Arena source code.
35
Quake III Arena source code is free software; you can redistribute it
36
and/or modify it under the terms of the GNU General Public License as
37
published by the Free Software Foundation; either version 2 of the License,
38
or (at your option) any later version.
40
Quake III Arena source code is distributed in the hope that it will be
41
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
42
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
43
GNU General Public License for more details.
45
You should have received a copy of the GNU General Public License
46
along with Quake III Arena source code; if not, write to the Free Software
47
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
48
===========================================================================
56
#include "../qcommon/q_shared.h"
57
#include "../client/snd_local.h"
59
qboolean snd_inited = qfalse;
63
cvar_t *s_sdlChannels;
64
cvar_t *s_sdlDevSamps;
65
cvar_t *s_sdlMixSamps;
67
static qboolean use_custom_memset = qfalse;
73
https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371
75
<TTimo> some shitty mess with DMA buffers
76
<TTimo> the mmap'ing permissions were write only
77
<TTimo> and glibc optimized for mmx would do memcpy with a prefetch and a read
78
<TTimo> causing segfaults
79
<TTimo> some other systems would not let you mmap the DMA with read permissions
80
<TTimo> so I think I ended up attempting opening with read/write, then try write only
81
<TTimo> and use my own copy instead of the glibc crap
88
void Snd_Memset (void* dest, const int val, const size_t count)
93
if (!use_custom_memset)
95
Com_Memset(dest,val,count);
98
iterate = count / sizeof(int);
100
for(i=0; i<iterate; i++)
106
/* The audio callback. All the magic happens here. */
107
static int dmapos = 0;
108
static int dmasize = 0;
115
static void sdl_audio_callback(void *userdata, Uint8 *stream, int len)
117
int pos = (dmapos * (dma.samplebits/8));
121
if (!snd_inited) /* shouldn't happen, but just in case... */
123
memset(stream, '\0', len);
128
int tobufend = dmasize - pos; /* bytes to buffer's end. */
137
memcpy(stream, dma.buffer + pos, len1);
139
dmapos += (len1 / (dma.samplebits/8));
140
else /* wraparound? */
142
memcpy(stream+len1, dma.buffer, len2);
143
dmapos = (len2 / (dma.samplebits/8));
147
if (dmapos >= dmasize)
155
} formatToStringTable[ ] =
157
{ AUDIO_U8, "AUDIO_U8" },
158
{ AUDIO_S8, "AUDIO_S8" },
159
{ AUDIO_U16LSB, "AUDIO_U16LSB" },
160
{ AUDIO_S16LSB, "AUDIO_S16LSB" },
161
{ AUDIO_U16MSB, "AUDIO_U16MSB" },
162
{ AUDIO_S16MSB, "AUDIO_S16MSB" }
165
static int formatToStringTableSize =
166
sizeof( formatToStringTable ) / sizeof( formatToStringTable[ 0 ] );
173
static void print_audiospec(const char *str, const SDL_AudioSpec *spec)
178
Com_Printf("%s:\n", str);
180
for( i = 0; i < formatToStringTableSize; i++ ) {
181
if( spec->format == formatToStringTable[ i ].enumFormat ) {
182
fmt = formatToStringTable[ i ].stringFormat;
187
Com_Printf( " Format: %s\n", fmt );
189
Com_Printf( " Format: " S_COLOR_RED "UNKNOWN\n", fmt );
192
Com_Printf( " Freq: %d\n", (int) spec->freq );
193
Com_Printf( " Samples: %d\n", (int) spec->samples );
194
Com_Printf( " Channels: %d\n", (int) spec->channels );
202
qboolean SNDDMA_Init(void)
204
char drivername[128];
205
SDL_AudioSpec desired;
206
SDL_AudioSpec obtained;
212
Com_Printf("Initializing SDL audio driver...\n");
215
s_sdlBits = Cvar_Get("s_sdlBits", "16", CVAR_ARCHIVE);
216
s_sdlSpeed = Cvar_Get("s_sdlSpeed", "0", CVAR_ARCHIVE);
217
s_sdlChannels = Cvar_Get("s_sdlChannels", "2", CVAR_ARCHIVE);
218
s_sdlDevSamps = Cvar_Get("s_sdlDevSamps", "0", CVAR_ARCHIVE);
219
s_sdlMixSamps = Cvar_Get("s_sdlMixSamps", "0", CVAR_ARCHIVE);
222
if (!SDL_WasInit(SDL_INIT_AUDIO))
224
if (SDL_Init(SDL_INIT_AUDIO) == -1)
226
Com_Printf("SDL_Init(SDL_INIT_AUDIO) failed: %s\n", SDL_GetError());
231
if (SDL_AudioDriverName(drivername, sizeof (drivername)) == NULL)
232
strcpy(drivername, "(UNKNOWN)");
233
Com_Printf("SDL audio driver is \"%s\".\n", drivername);
235
memset(&desired, '\0', sizeof (desired));
236
memset(&obtained, '\0', sizeof (obtained));
238
tmp = ((int) s_sdlBits->value);
239
if ((tmp != 16) && (tmp != 8))
242
desired.freq = (int) s_sdlSpeed->value;
243
if(!desired.freq) desired.freq = 22050;
244
desired.format = ((tmp == 16) ? AUDIO_S16SYS : AUDIO_U8);
246
// I dunno if this is the best idea, but I'll give it a try...
247
// should probably check a cvar for this...
248
if (s_sdlDevSamps->value)
249
desired.samples = s_sdlDevSamps->value;
252
// just pick a sane default.
253
if (desired.freq <= 11025)
254
desired.samples = 256;
255
else if (desired.freq <= 22050)
256
desired.samples = 512;
257
else if (desired.freq <= 44100)
258
desired.samples = 1024;
260
desired.samples = 2048; // (*shrug*)
263
desired.channels = (int) s_sdlChannels->value;
264
desired.callback = sdl_audio_callback;
266
if (SDL_OpenAudio(&desired, &obtained) == -1)
268
Com_Printf("SDL_OpenAudio() failed: %s\n", SDL_GetError());
269
SDL_QuitSubSystem(SDL_INIT_AUDIO);
273
print_audiospec("SDL_AudioSpec", &obtained);
275
// dma.samples needs to be big, or id's mixer will just refuse to
276
// work at all; we need to keep it significantly bigger than the
277
// amount of SDL callback samples, and just copy a little each time
278
// the callback runs.
279
// 32768 is what the OSS driver filled in here on my system. I don't
280
// know if it's a good value overall, but at least we know it's
281
// reasonable...this is why I let the user override.
282
tmp = s_sdlMixSamps->value;
284
tmp = (obtained.samples * obtained.channels) * 10;
286
if (tmp & (tmp - 1)) // not a power of two? Seems to confuse something.
296
dma.samplebits = obtained.format & 0xFF; // first byte of format is bits.
297
dma.channels = obtained.channels;
299
dma.submission_chunk = 1;
300
dma.speed = obtained.freq;
301
dmasize = (dma.samples * (dma.samplebits/8));
302
dma.buffer = calloc(1, dmasize);
304
Com_Printf("Starting SDL audio callback...\n");
305
SDL_PauseAudio(0); // start callback.
307
Com_Printf("SDL audio initialized.\n");
317
int SNDDMA_GetDMAPos(void)
327
void SNDDMA_Shutdown(void)
329
Com_Printf("Closing SDL audio device...\n");
332
SDL_QuitSubSystem(SDL_INIT_AUDIO);
335
dmapos = dmasize = 0;
337
Com_Printf("SDL audio device shut down.\n");
344
Send sound to device if buffer isn't really the dma buffer
347
void SNDDMA_Submit(void)
357
void SNDDMA_BeginPainting (void)
362
#endif // USE_SDL_SOUND