cnq3/code/unix/sdl_snd.c
2016-12-17 20:43:04 -08:00

362 lines
8.8 KiB
C

#if USE_SDL_SOUND
/*
* SDL implementation for Quake 3: Arena's GPL source release.
*
* This is a replacement of the Linux/OpenSoundSystem code with
* an SDL backend, since it allows us to trivially point just about any
* existing 2D audio backend known to man on any platform at the code,
* plus it benefits from all of SDL's tapdancing to support buggy drivers,
* etc, and gets us free ALSA support, too.
*
* This is the best idea for a direct modernization of the Linux sound code
* in Quake 3. However, it would be nice to replace this with true 3D
* positional audio, compliments of OpenAL...
*
* Written by Ryan C. Gordon (icculus@icculus.org). Please refer to
* http://icculus.org/quake3/ for the latest version of this code.
*
* Patches and comments are welcome at the above address.
*
* I cut-and-pasted this from linux_snd.c, and moved it to SDL line-by-line.
* There is probably some cruft that could be removed here.
*
* You should define USE_SDL=1 and then add this to the makefile.
* USE_SDL will disable the Open Sound System target.
*/
/*
Original copyright on Q3A sources:
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include <stdlib.h>
#include <stdio.h>
#include "SDL.h"
#include "../qcommon/q_shared.h"
#include "../client/snd_local.h"
qboolean snd_inited = qfalse;
cvar_t *s_sdlBits;
cvar_t *s_sdlSpeed;
cvar_t *s_sdlChannels;
cvar_t *s_sdlDevSamps;
cvar_t *s_sdlMixSamps;
static qboolean use_custom_memset = qfalse;
/*
===============
Snd_Memset
https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371
<TTimo> some shitty mess with DMA buffers
<TTimo> the mmap'ing permissions were write only
<TTimo> and glibc optimized for mmx would do memcpy with a prefetch and a read
<TTimo> causing segfaults
<TTimo> some other systems would not let you mmap the DMA with read permissions
<TTimo> so I think I ended up attempting opening with read/write, then try write only
<TTimo> and use my own copy instead of the glibc crap
===============
*/
#ifdef Snd_Memset
#undef Snd_Memset
#endif
void Snd_Memset (void* dest, const int val, const size_t count)
{
int *pDest;
int i, iterate;
if (!use_custom_memset)
{
Com_Memset(dest,val,count);
return;
}
iterate = count / sizeof(int);
pDest = (int*)dest;
for(i=0; i<iterate; i++)
{
pDest[i] = val;
}
}
/* The audio callback. All the magic happens here. */
static int dmapos = 0;
static int dmasize = 0;
/*
===============
sdl_audio_callback
===============
*/
static void sdl_audio_callback(void *userdata, Uint8 *stream, int len)
{
int pos = (dmapos * (dma.samplebits/8));
if (pos >= dmasize)
dmapos = pos = 0;
if (!snd_inited) /* shouldn't happen, but just in case... */
{
memset(stream, '\0', len);
return;
}
else
{
int tobufend = dmasize - pos; /* bytes to buffer's end. */
int len1 = len;
int len2 = 0;
if (len1 > tobufend)
{
len1 = tobufend;
len2 = len - len1;
}
memcpy(stream, dma.buffer + pos, len1);
if (len2 <= 0)
dmapos += (len1 / (dma.samplebits/8));
else /* wraparound? */
{
memcpy(stream+len1, dma.buffer, len2);
dmapos = (len2 / (dma.samplebits/8));
}
}
if (dmapos >= dmasize)
dmapos = 0;
}
static struct
{
Uint16 enumFormat;
char *stringFormat;
} formatToStringTable[ ] =
{
{ AUDIO_U8, "AUDIO_U8" },
{ AUDIO_S8, "AUDIO_S8" },
{ AUDIO_U16LSB, "AUDIO_U16LSB" },
{ AUDIO_S16LSB, "AUDIO_S16LSB" },
{ AUDIO_U16MSB, "AUDIO_U16MSB" },
{ AUDIO_S16MSB, "AUDIO_S16MSB" }
};
static int formatToStringTableSize =
sizeof( formatToStringTable ) / sizeof( formatToStringTable[ 0 ] );
/*
===============
print_audiospec
===============
*/
static void print_audiospec(const char *str, const SDL_AudioSpec *spec)
{
int i;
char *fmt = NULL;
Com_Printf("%s:\n", str);
for( i = 0; i < formatToStringTableSize; i++ ) {
if( spec->format == formatToStringTable[ i ].enumFormat ) {
fmt = formatToStringTable[ i ].stringFormat;
}
}
if( fmt ) {
Com_Printf( " Format: %s\n", fmt );
} else {
Com_Printf( " Format: " S_COLOR_RED "UNKNOWN\n", fmt );
}
Com_Printf( " Freq: %d\n", (int) spec->freq );
Com_Printf( " Samples: %d\n", (int) spec->samples );
Com_Printf( " Channels: %d\n", (int) spec->channels );
}
/*
===============
SNDDMA_Init
===============
*/
qboolean SNDDMA_Init(void)
{
char drivername[128];
SDL_AudioSpec desired;
SDL_AudioSpec obtained;
int tmp;
if (snd_inited)
return qtrue;
Com_Printf("Initializing SDL audio driver...\n");
if (!s_sdlBits) {
s_sdlBits = Cvar_Get("s_sdlBits", "16", CVAR_ARCHIVE);
s_sdlSpeed = Cvar_Get("s_sdlSpeed", "0", CVAR_ARCHIVE);
s_sdlChannels = Cvar_Get("s_sdlChannels", "2", CVAR_ARCHIVE);
s_sdlDevSamps = Cvar_Get("s_sdlDevSamps", "0", CVAR_ARCHIVE);
s_sdlMixSamps = Cvar_Get("s_sdlMixSamps", "0", CVAR_ARCHIVE);
}
if (!SDL_WasInit(SDL_INIT_AUDIO))
{
if (SDL_Init(SDL_INIT_AUDIO) == -1)
{
Com_Printf("SDL_Init(SDL_INIT_AUDIO) failed: %s\n", SDL_GetError());
return qfalse;
}
}
if (SDL_AudioDriverName(drivername, sizeof (drivername)) == NULL)
strcpy(drivername, "(UNKNOWN)");
Com_Printf("SDL audio driver is \"%s\".\n", drivername);
memset(&desired, '\0', sizeof (desired));
memset(&obtained, '\0', sizeof (obtained));
tmp = ((int) s_sdlBits->value);
if ((tmp != 16) && (tmp != 8))
tmp = 16;
desired.freq = (int) s_sdlSpeed->value;
if(!desired.freq) desired.freq = 22050;
desired.format = ((tmp == 16) ? AUDIO_S16SYS : AUDIO_U8);
// I dunno if this is the best idea, but I'll give it a try...
// should probably check a cvar for this...
if (s_sdlDevSamps->value)
desired.samples = s_sdlDevSamps->value;
else
{
// just pick a sane default.
if (desired.freq <= 11025)
desired.samples = 256;
else if (desired.freq <= 22050)
desired.samples = 512;
else if (desired.freq <= 44100)
desired.samples = 1024;
else
desired.samples = 2048; // (*shrug*)
}
desired.channels = (int) s_sdlChannels->value;
desired.callback = sdl_audio_callback;
if (SDL_OpenAudio(&desired, &obtained) == -1)
{
Com_Printf("SDL_OpenAudio() failed: %s\n", SDL_GetError());
SDL_QuitSubSystem(SDL_INIT_AUDIO);
return qfalse;
} // if
print_audiospec("SDL_AudioSpec", &obtained);
// dma.samples needs to be big, or id's mixer will just refuse to
// work at all; we need to keep it significantly bigger than the
// amount of SDL callback samples, and just copy a little each time
// the callback runs.
// 32768 is what the OSS driver filled in here on my system. I don't
// know if it's a good value overall, but at least we know it's
// reasonable...this is why I let the user override.
tmp = s_sdlMixSamps->value;
if (!tmp)
tmp = (obtained.samples * obtained.channels) * 10;
if (tmp & (tmp - 1)) // not a power of two? Seems to confuse something.
{
int val = 1;
while (val < tmp)
val <<= 1;
tmp = val;
}
dmapos = 0;
dma.samplebits = obtained.format & 0xFF; // first byte of format is bits.
dma.channels = obtained.channels;
dma.samples = tmp;
dma.submission_chunk = 1;
dma.speed = obtained.freq;
dmasize = (dma.samples * (dma.samplebits/8));
dma.buffer = (byte*)calloc(1, dmasize);
Com_Printf("Starting SDL audio callback...\n");
SDL_PauseAudio(0); // start callback.
Com_Printf("SDL audio initialized.\n");
snd_inited = qtrue;
return qtrue;
}
/*
===============
SNDDMA_GetDMAPos
===============
*/
int SNDDMA_GetDMAPos(void)
{
return dmapos;
}
/*
===============
SNDDMA_Shutdown
===============
*/
void SNDDMA_Shutdown(void)
{
Com_Printf("Closing SDL audio device...\n");
SDL_PauseAudio(1);
SDL_CloseAudio();
SDL_QuitSubSystem(SDL_INIT_AUDIO);
free(dma.buffer);
dma.buffer = NULL;
dmapos = dmasize = 0;
snd_inited = qfalse;
Com_Printf("SDL audio device shut down.\n");
}
/*
===============
SNDDMA_Submit
Send sound to device if buffer isn't really the dma buffer
===============
*/
void SNDDMA_Submit(void)
{
SDL_UnlockAudio();
}
/*
===============
SNDDMA_BeginPainting
===============
*/
void SNDDMA_BeginPainting (void)
{
SDL_LockAudio();
}
#endif // USE_SDL_SOUND