2011-02-18 14:31:32 +00:00
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
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>
# ifdef USE_LOCAL_HEADERS
# include "SDL.h"
# else
# include <SDL.h>
# endif
# include "../qcommon/q_shared.h"
# include "../client/snd_local.h"
2018-07-30 11:35:12 +00:00
# include "../client/client.h"
2011-02-18 14:31:32 +00:00
qboolean snd_inited = qfalse ;
cvar_t * s_sdlBits ;
cvar_t * s_sdlSpeed ;
cvar_t * s_sdlChannels ;
cvar_t * s_sdlDevSamps ;
cvar_t * s_sdlMixSamps ;
/* The audio callback. All the magic happens here. */
static int dmapos = 0 ;
static int dmasize = 0 ;
2018-07-30 11:35:12 +00:00
static SDL_AudioDeviceID sdlPlaybackDevice ;
# if defined USE_VOIP && SDL_VERSION_ATLEAST( 2, 0, 5 )
# define USE_SDL_AUDIO_CAPTURE
static SDL_AudioDeviceID sdlCaptureDevice ;
static cvar_t * s_sdlCapture ;
static float sdlMasterGain = 1.0f ;
# endif
2011-02-18 14:31:32 +00:00
/*
= = = = = = = = = = = = = = =
SNDDMA_AudioCallback
= = = = = = = = = = = = = = =
*/
static void SNDDMA_AudioCallback ( 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 ;
2018-07-30 11:35:12 +00:00
# ifdef USE_SDL_AUDIO_CAPTURE
if ( sdlMasterGain ! = 1.0f )
{
int i ;
if ( dma . isfloat & & ( dma . samplebits = = 32 ) )
{
float * ptr = ( float * ) stream ;
len / = sizeof ( * ptr ) ;
for ( i = 0 ; i < len ; i + + , ptr + + )
{
* ptr * = sdlMasterGain ;
}
}
else if ( dma . samplebits = = 16 )
{
Sint16 * ptr = ( Sint16 * ) stream ;
len / = sizeof ( * ptr ) ;
for ( i = 0 ; i < len ; i + + , ptr + + )
{
* ptr = ( Sint16 ) ( ( ( float ) * ptr ) * sdlMasterGain ) ;
}
}
else if ( dma . samplebits = = 8 )
{
Uint8 * ptr = ( Uint8 * ) stream ;
len / = sizeof ( * ptr ) ;
for ( i = 0 ; i < len ; i + + , ptr + + )
{
* ptr = ( Uint8 ) ( ( ( float ) * ptr ) * sdlMasterGain ) ;
}
}
}
# endif
2011-02-18 14:31:32 +00:00
}
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 " } ,
2018-07-30 11:35:12 +00:00
{ AUDIO_S16MSB , " AUDIO_S16MSB " } ,
{ AUDIO_F32LSB , " AUDIO_F32LSB " } ,
{ AUDIO_F32MSB , " AUDIO_F32MSB " }
2011-02-18 14:31:32 +00:00
} ;
2011-07-26 08:52:24 +00:00
static int formatToStringTableSize = ARRAY_LEN ( formatToStringTable ) ;
2011-02-18 14:31:32 +00:00
/*
= = = = = = = = = = = = = = =
SNDDMA_PrintAudiospec
= = = = = = = = = = = = = = =
*/
static void SNDDMA_PrintAudiospec ( 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 " ) ;
}
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 )
{
SDL_AudioSpec desired ;
SDL_AudioSpec obtained ;
int tmp ;
2018-07-30 11:35:12 +00:00
# ifdef USE_SDL_AUDIO_CAPTURE
SDL_version sdlVersion ;
# endif
2011-02-18 14:31:32 +00:00
if ( snd_inited )
return qtrue ;
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 ) ;
}
Com_Printf ( " SDL_Init( SDL_INIT_AUDIO )... " ) ;
2018-07-30 11:35:12 +00:00
if ( SDL_Init ( SDL_INIT_AUDIO ) ! = 0 )
2011-02-18 14:31:32 +00:00
{
2018-07-30 11:35:12 +00:00
Com_Printf ( " FAILED (%s) \n " , SDL_GetError ( ) ) ;
return qfalse ;
2011-02-18 14:31:32 +00:00
}
Com_Printf ( " OK \n " ) ;
2017-07-10 01:33:41 +00:00
Com_Printf ( " SDL audio driver is \" %s \" . \n " , SDL_GetCurrentAudioDriver ( ) ) ;
2011-02-18 14:31:32 +00:00
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 = SNDDMA_AudioCallback ;
2018-07-30 11:35:12 +00:00
sdlPlaybackDevice = SDL_OpenAudioDevice ( NULL , SDL_FALSE , & desired , & obtained , SDL_AUDIO_ALLOW_ANY_CHANGE ) ;
if ( sdlPlaybackDevice = = 0 )
2011-02-18 14:31:32 +00:00
{
2018-07-30 11:35:12 +00:00
Com_Printf ( " SDL_OpenAudioDevice() failed: %s \n " , SDL_GetError ( ) ) ;
2011-02-18 14:31:32 +00:00
SDL_QuitSubSystem ( SDL_INIT_AUDIO ) ;
return qfalse ;
}
SNDDMA_PrintAudiospec ( " 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 ;
2018-07-30 11:35:12 +00:00
dma . samplebits = SDL_AUDIO_BITSIZE ( obtained . format ) ;
dma . isfloat = SDL_AUDIO_ISFLOAT ( obtained . format ) ;
2011-02-18 14:31:32 +00:00
dma . channels = obtained . channels ;
dma . samples = tmp ;
dma . submission_chunk = 1 ;
dma . speed = obtained . freq ;
dmasize = ( dma . samples * ( dma . samplebits / 8 ) ) ;
dma . buffer = calloc ( 1 , dmasize ) ;
2018-07-30 11:35:12 +00:00
# ifdef USE_SDL_AUDIO_CAPTURE
// !!! FIXME: some of these SDL_OpenAudioDevice() values should be cvars.
s_sdlCapture = Cvar_Get ( " s_sdlCapture " , " 1 " , CVAR_ARCHIVE | CVAR_LATCH ) ;
// !!! FIXME: hopefully pulseaudio capture will be fixed in SDL 2.0.9... https://bugzilla.libsdl.org/show_bug.cgi?id=4087
SDL_GetVersion ( & sdlVersion ) ;
if ( sdlVersion . major = = 2 & & sdlVersion . minor = = 0 & & sdlVersion . patch < 9 & & Q_stricmp ( SDL_GetCurrentAudioDriver ( ) , " pulseaudio " ) = = 0 )
{
Com_Printf ( " SDL audio capture support disabled (pulseaudio capture does not work correctly with SDL %d.%d.%d) \n " , sdlVersion . major , sdlVersion . minor , sdlVersion . patch ) ;
}
else if ( ! s_sdlCapture - > integer )
{
Com_Printf ( " SDL audio capture support disabled by user ('+set s_sdlCapture 1' to enable) \n " ) ;
}
# if USE_MUMBLE
else if ( cl_useMumble - > integer )
{
Com_Printf ( " SDL audio capture support disabled for Mumble support \n " ) ;
}
# endif
else
{
/* !!! FIXME: list available devices and let cvar specify one, like OpenAL does */
SDL_AudioSpec spec ;
SDL_zero ( spec ) ;
spec . freq = 48000 ;
spec . format = AUDIO_S16SYS ;
spec . channels = 1 ;
spec . samples = VOIP_MAX_PACKET_SAMPLES * 4 ;
sdlCaptureDevice = SDL_OpenAudioDevice ( NULL , SDL_TRUE , & spec , NULL , 0 ) ;
Com_Printf ( " SDL capture device %s. \n " ,
( sdlCaptureDevice = = 0 ) ? " failed to open " : " opened " ) ;
}
sdlMasterGain = 1.0f ;
# endif
2011-02-18 14:31:32 +00:00
Com_Printf ( " Starting SDL audio callback... \n " ) ;
2018-07-30 11:35:12 +00:00
SDL_PauseAudioDevice ( sdlPlaybackDevice , 0 ) ; // start callback.
// don't unpause the capture device; we'll do that in StartCapture.
2011-02-18 14:31:32 +00:00
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 )
{
2018-07-30 11:35:12 +00:00
if ( sdlPlaybackDevice ! = 0 )
{
Com_Printf ( " Closing SDL audio playback device... \n " ) ;
SDL_CloseAudioDevice ( sdlPlaybackDevice ) ;
Com_Printf ( " SDL audio playback device closed. \n " ) ;
sdlPlaybackDevice = 0 ;
}
# ifdef USE_SDL_AUDIO_CAPTURE
if ( sdlCaptureDevice )
{
Com_Printf ( " Closing SDL audio capture device... \n " ) ;
SDL_CloseAudioDevice ( sdlCaptureDevice ) ;
Com_Printf ( " SDL audio capture device closed. \n " ) ;
sdlCaptureDevice = 0 ;
}
# endif
2011-02-18 14:31:32 +00:00
SDL_QuitSubSystem ( SDL_INIT_AUDIO ) ;
free ( dma . buffer ) ;
dma . buffer = NULL ;
dmapos = dmasize = 0 ;
snd_inited = qfalse ;
2018-07-30 11:35:12 +00:00
Com_Printf ( " SDL audio shut down. \n " ) ;
2011-02-18 14:31:32 +00:00
}
/*
= = = = = = = = = = = = = = =
SNDDMA_Submit
Send sound to device if buffer isn ' t really the dma buffer
= = = = = = = = = = = = = = =
*/
void SNDDMA_Submit ( void )
{
2018-07-30 11:35:12 +00:00
SDL_UnlockAudioDevice ( sdlPlaybackDevice ) ;
2011-02-18 14:31:32 +00:00
}
/*
= = = = = = = = = = = = = = =
SNDDMA_BeginPainting
= = = = = = = = = = = = = = =
*/
void SNDDMA_BeginPainting ( void )
{
2018-07-30 11:35:12 +00:00
SDL_LockAudioDevice ( sdlPlaybackDevice ) ;
}
# ifdef USE_VOIP
void SNDDMA_StartCapture ( void )
{
# ifdef USE_SDL_AUDIO_CAPTURE
if ( sdlCaptureDevice )
{
SDL_ClearQueuedAudio ( sdlCaptureDevice ) ;
SDL_PauseAudioDevice ( sdlCaptureDevice , 0 ) ;
}
# endif
}
int SNDDMA_AvailableCaptureSamples ( void )
{
# ifdef USE_SDL_AUDIO_CAPTURE
// divided by 2 to convert from bytes to (mono16) samples.
return sdlCaptureDevice ? ( SDL_GetQueuedAudioSize ( sdlCaptureDevice ) / 2 ) : 0 ;
# else
return 0 ;
# endif
}
void SNDDMA_Capture ( int samples , byte * data )
{
# ifdef USE_SDL_AUDIO_CAPTURE
// multiplied by 2 to convert from (mono16) samples to bytes.
if ( sdlCaptureDevice )
{
SDL_DequeueAudio ( sdlCaptureDevice , data , samples * 2 ) ;
}
else
# endif
{
SDL_memset ( data , ' \0 ' , samples * 2 ) ;
}
}
void SNDDMA_StopCapture ( void )
{
# ifdef USE_SDL_AUDIO_CAPTURE
if ( sdlCaptureDevice )
{
SDL_PauseAudioDevice ( sdlCaptureDevice , 1 ) ;
}
# endif
2011-02-18 14:31:32 +00:00
}
2018-07-30 11:35:12 +00:00
void SNDDMA_MasterGain ( float val )
{
# ifdef USE_SDL_AUDIO_CAPTURE
sdlMasterGain = val ;
# endif
}
# endif