Add SDL sound backend

This commit is contained in:
Alibek Omarov 2018-04-17 03:44:53 +03:00
parent fc7ed1ab75
commit 5744aa8d9e
4 changed files with 277 additions and 486 deletions

View File

@ -1,483 +0,0 @@
/*
s_backend.c - sound hardware output
Copyright (C) 2009 Uncle Mike
This program 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 3 of the License, or
(at your option) any later version.
This program 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.
*/
#include "common.h"
#include "sound.h"
#include <dsound.h>
#define iDirectSoundCreate( a, b, c ) pDirectSoundCreate( a, b, c )
static HRESULT ( _stdcall *pDirectSoundCreate)(GUID* lpGUID, LPDIRECTSOUND* lpDS, IUnknown* pUnkOuter );
static dllfunc_t dsound_funcs[] =
{
{ "DirectSoundCreate", (void **) &pDirectSoundCreate },
{ NULL, NULL }
};
dll_info_t dsound_dll = { "dsound.dll", dsound_funcs, false };
#define SAMPLE_16BIT_SHIFT 1
#define SECONDARY_BUFFER_SIZE 0x10000
typedef enum
{
SIS_SUCCESS,
SIS_FAILURE,
SIS_NOTAVAIL
} si_state_t;
static qboolean snd_firsttime = true;
static qboolean primary_format_set;
static HWND snd_hwnd;
/*
=======================================================================
Global variables. Must be visible to window-procedure function
so it can unlock and free the data block after it has been played.
=======================================================================
*/
static DWORD locksize;
static HPSTR lpData, lpData2;
static DWORD gSndBufSize;
static MMTIME mmstarttime;
static LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf;
static LPDIRECTSOUND pDS;
qboolean SNDDMA_InitDirect( void *hInst );
void SNDDMA_FreeSound( void );
static const char *DSoundError( int error )
{
switch( error )
{
case DSERR_BUFFERLOST:
return "buffer is lost";
case DSERR_INVALIDCALL:
return "invalid call";
case DSERR_INVALIDPARAM:
return "invalid param";
case DSERR_PRIOLEVELNEEDED:
return "invalid priority level";
}
return "unknown error";
}
/*
==================
DS_CreateBuffers
==================
*/
static qboolean DS_CreateBuffers( void *hInst )
{
WAVEFORMATEX pformat, format;
DSBCAPS dsbcaps;
DSBUFFERDESC dsbuf;
memset( &format, 0, sizeof( format ));
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = 2;
format.wBitsPerSample = 16;
format.nSamplesPerSec = SOUND_DMA_SPEED;
format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
format.cbSize = 0;
if( pDS->lpVtbl->SetCooperativeLevel( pDS, hInst, DSSCL_EXCLUSIVE ) != DS_OK )
{
Con_DPrintf( S_ERROR "DirectSound: failed to set EXCLUSIVE coop level\n" );
SNDDMA_FreeSound();
return false;
}
// get access to the primary buffer, if possible, so we can set the sound hardware format
memset( &dsbuf, 0, sizeof( dsbuf ));
dsbuf.dwSize = sizeof( DSBUFFERDESC );
dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER;
dsbuf.dwBufferBytes = 0;
dsbuf.lpwfxFormat = NULL;
memset( &dsbcaps, 0, sizeof( dsbcaps ));
dsbcaps.dwSize = sizeof( dsbcaps );
primary_format_set = false;
if( pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSPBuf, NULL ) == DS_OK )
{
pformat = format;
if( pDSPBuf->lpVtbl->SetFormat( pDSPBuf, &pformat ) != DS_OK )
{
if( snd_firsttime )
Con_DPrintf( S_ERROR "DirectSound: failed to set primary sound format\n" );
}
else
{
primary_format_set = true;
}
}
// create the secondary buffer we'll actually work with
memset( &dsbuf, 0, sizeof( dsbuf ));
dsbuf.dwSize = sizeof( DSBUFFERDESC );
dsbuf.dwFlags = (DSBCAPS_CTRLFREQUENCY|DSBCAPS_LOCSOFTWARE);
dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE;
dsbuf.lpwfxFormat = &format;
memset( &dsbcaps, 0, sizeof( dsbcaps ));
dsbcaps.dwSize = sizeof( dsbcaps );
if( pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSBuf, NULL ) != DS_OK )
{
// couldn't get hardware, fallback to software.
dsbuf.dwFlags = (DSBCAPS_LOCSOFTWARE|DSBCAPS_GETCURRENTPOSITION2);
if( pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSBuf, NULL ) != DS_OK )
{
Con_DPrintf( S_ERROR "DirectSound: failed to create secondary buffer\n" );
SNDDMA_FreeSound ();
return false;
}
}
if( pDSBuf->lpVtbl->GetCaps( pDSBuf, &dsbcaps ) != DS_OK )
{
Con_DPrintf( S_ERROR "DirectSound: failed to get capabilities\n" );
SNDDMA_FreeSound ();
return false;
}
// make sure mixer is active
if( pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING ) != DS_OK )
{
Con_DPrintf( S_ERROR "DirectSound: failed to create circular buffer\n" );
SNDDMA_FreeSound ();
return false;
}
// we don't want anyone to access the buffer directly w/o locking it first
lpData = NULL;
dma.samplepos = 0;
snd_hwnd = (HWND)hInst;
gSndBufSize = dsbcaps.dwBufferBytes;
dma.samples = gSndBufSize / 2;
dma.buffer = (byte *)lpData;
SNDDMA_BeginPainting();
if( dma.buffer ) memset( dma.buffer, 0, dma.samples * 2 );
SNDDMA_Submit();
return true;
}
/*
==================
DS_DestroyBuffers
==================
*/
static void DS_DestroyBuffers( void )
{
if( pDS ) pDS->lpVtbl->SetCooperativeLevel( pDS, snd_hwnd, DSSCL_NORMAL );
if( pDSBuf )
{
pDSBuf->lpVtbl->Stop( pDSBuf );
pDSBuf->lpVtbl->Release( pDSBuf );
}
// only release primary buffer if it's not also the mixing buffer we just released
if( pDSPBuf && ( pDSBuf != pDSPBuf ))
pDSPBuf->lpVtbl->Release( pDSPBuf );
dma.buffer = NULL;
pDSPBuf = NULL;
pDSBuf = NULL;
}
/*
==================
SNDDMA_LockSound
==================
*/
void SNDDMA_LockSound( void )
{
if( pDSBuf != NULL )
pDSBuf->lpVtbl->Stop( pDSBuf );
}
/*
==================
SNDDMA_LockSound
==================
*/
void SNDDMA_UnlockSound( void )
{
if( pDSBuf != NULL )
pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING );
}
/*
==================
SNDDMA_FreeSound
==================
*/
void SNDDMA_FreeSound( void )
{
if( pDS )
{
DS_DestroyBuffers();
pDS->lpVtbl->Release( pDS );
Sys_FreeLibrary( &dsound_dll );
}
lpData = NULL;
pDSPBuf = NULL;
pDSBuf = NULL;
pDS = NULL;
}
/*
==================
SNDDMA_InitDirect
Direct-Sound support
==================
*/
si_state_t SNDDMA_InitDirect( void *hInst )
{
DSCAPS dscaps;
HRESULT hresult;
if( !dsound_dll.link )
{
if( !Sys_LoadLibrary( &dsound_dll ))
return SIS_FAILURE;
}
if(( hresult = iDirectSoundCreate( NULL, &pDS, NULL )) != DS_OK )
{
if( hresult != DSERR_ALLOCATED )
return SIS_FAILURE;
Con_DPrintf( S_ERROR "DirectSound: hardware already in use\n" );
return SIS_NOTAVAIL;
}
dscaps.dwSize = sizeof( dscaps );
if( pDS->lpVtbl->GetCaps( pDS, &dscaps ) != DS_OK )
Con_DPrintf( S_ERROR "DirectSound: failed to get capabilities\n" );
if( FBitSet( dscaps.dwFlags, DSCAPS_EMULDRIVER ))
{
Con_DPrintf( S_ERROR "DirectSound: driver not installed\n" );
SNDDMA_FreeSound();
return SIS_FAILURE;
}
if( !DS_CreateBuffers( hInst ))
return SIS_FAILURE;
return SIS_SUCCESS;
}
/*
==================
SNDDMA_Init
Try to find a sound device to mix for.
Returns false if nothing is found.
==================
*/
int SNDDMA_Init( void *hInst )
{
// already initialized
if( dma.initialized ) return true;
memset( &dma, 0, sizeof( dma ));
// init DirectSound
if( SNDDMA_InitDirect( hInst ) != SIS_SUCCESS )
return false;
if( snd_firsttime )
Con_Printf( "Audio: DirectSound\n" );
dma.initialized = true;
snd_firsttime = false;
return true;
}
/*
==============
SNDDMA_GetDMAPos
return the current sample position (in mono samples read)
inside the recirculating dma buffer, so the mixing code will know
how many sample are required to fill it up.
===============
*/
int SNDDMA_GetDMAPos( void )
{
int s;
MMTIME mmtime;
DWORD dwWrite;
if( !dma.initialized )
return 0;
mmtime.wType = TIME_SAMPLES;
pDSBuf->lpVtbl->GetCurrentPosition( pDSBuf, &mmtime.u.sample, &dwWrite );
s = mmtime.u.sample - mmstarttime.u.sample;
s >>= SAMPLE_16BIT_SHIFT;
s &= (dma.samples - 1);
return s;
}
/*
==============
SNDDMA_GetSoundtime
update global soundtime
===============
*/
int SNDDMA_GetSoundtime( void )
{
static int buffers, oldsamplepos;
int samplepos, fullsamples;
fullsamples = dma.samples / 2;
// it is possible to miscount buffers
// if it has wrapped twice between
// calls to S_Update. Oh well.
samplepos = SNDDMA_GetDMAPos();
if( samplepos < oldsamplepos )
{
buffers++; // buffer wrapped
if( paintedtime > 0x40000000 )
{
// time to chop things off to avoid 32 bit limits
buffers = 0;
paintedtime = fullsamples;
S_StopAllSounds( true );
}
}
oldsamplepos = samplepos;
return (buffers * fullsamples + samplepos / 2);
}
/*
==============
SNDDMA_BeginPainting
Makes sure dma.buffer is valid
===============
*/
void SNDDMA_BeginPainting( void )
{
int reps;
DWORD dwSize2;
DWORD *pbuf, *pbuf2;
HRESULT hr;
DWORD dwStatus;
if( !pDSBuf ) return;
// if the buffer was lost or stopped, restore it and/or restart it
if( pDSBuf->lpVtbl->GetStatus( pDSBuf, &dwStatus ) != DS_OK )
Con_DPrintf( S_ERROR "BeginPainting: couldn't get sound buffer status\n" );
if( dwStatus & DSBSTATUS_BUFFERLOST )
pDSBuf->lpVtbl->Restore( pDSBuf );
if( !FBitSet( dwStatus, DSBSTATUS_PLAYING ))
pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING );
// lock the dsound buffer
dma.buffer = NULL;
reps = 0;
while(( hr = pDSBuf->lpVtbl->Lock( pDSBuf, 0, gSndBufSize, &pbuf, &locksize, &pbuf2, &dwSize2, 0 )) != DS_OK )
{
if( hr != DSERR_BUFFERLOST )
{
Con_DPrintf( S_ERROR "BeginPainting: %s\n", DSoundError( hr ));
S_Shutdown ();
return;
}
else pDSBuf->lpVtbl->Restore( pDSBuf );
if( ++reps > 2 ) return;
}
dma.buffer = (byte *)pbuf;
}
/*
==============
SNDDMA_Submit
Send sound to device if buffer isn't really the dma buffer
Also unlocks the dsound buffer
===============
*/
void SNDDMA_Submit( void )
{
if( !dma.buffer ) return;
// unlock the dsound buffer
if( pDSBuf ) pDSBuf->lpVtbl->Unlock( pDSBuf, dma.buffer, locksize, NULL, 0 );
}
/*
==============
SNDDMA_Shutdown
Reset the sound device for exiting
===============
*/
void SNDDMA_Shutdown( void )
{
if( !dma.initialized ) return;
dma.initialized = false;
SNDDMA_FreeSound();
}
/*
===========
S_Activate
Called when the main window gains or loses focus.
The window have been destroyed and recreated
between a deactivate and an activate.
===========
*/
void S_Activate( qboolean active, void *hInst )
{
if( !dma.initialized ) return;
snd_hwnd = (HWND)hInst;
if( !pDS || !snd_hwnd )
return;
if( active )
DS_CreateBuffers( snd_hwnd );
else DS_DestroyBuffers();
}

View File

@ -50,11 +50,13 @@ convar_t *snd_foliage_db_loss;
convar_t *snd_gain;
convar_t *snd_gain_max;
convar_t *snd_gain_min;
convar_t *snd_mute_losefocus;
convar_t *s_refdist;
convar_t *s_refdb;
convar_t *s_cull; // cull sounds by geometry
convar_t *s_test; // cvar for testing new effects
convar_t *s_phs;
convar_t *s_samplecount;
/*
=============================================================================
@ -2172,12 +2174,14 @@ qboolean S_Init( void )
snd_foliage_db_loss = Cvar_Get( "snd_foliage_db_loss", "4", 0, "foliage loss factor" );
snd_gain_max = Cvar_Get( "snd_gain_max", "1", 0, "gain maximal threshold" );
snd_gain_min = Cvar_Get( "snd_gain_min", "0.01", 0, "gain minimal threshold" );
snd_mute_losefocus = Cvar_Get( "snd_mute_losefocus", "1", FCVAR_ARCHIVE, "silence the audio when game window loses focus" );
s_refdist = Cvar_Get( "s_refdist", "36", 0, "soundlevel reference distance" );
s_refdb = Cvar_Get( "s_refdb", "60", 0, "soundlevel refernce dB" );
snd_gain = Cvar_Get( "snd_gain", "1", 0, "sound default gain" );
s_cull = Cvar_Get( "s_cull", "0", FCVAR_ARCHIVE, "cull sounds by geometry" );
s_test = Cvar_Get( "s_test", "0", 0, "engine developer cvar for quick testing new features" );
s_phs = Cvar_Get( "s_phs", "0", FCVAR_ARCHIVE, "cull sounds by PHS" );
s_samplecount = Cvar_Get( "s_samplecount", "0", FCVAR_ARCHIVE, "sample count (0 for default value)" );
Cmd_AddCommand( "play", S_Play_f, "playing a specified sound file" );
Cmd_AddCommand( "playvol", S_PlayVol_f, "playing a specified sound file with specified volume" );
@ -2241,4 +2245,4 @@ void S_Shutdown( void )
SNDDMA_Shutdown ();
MIX_FreeAllPaintbuffers ();
Mem_FreePool( &sndpool );
}
}

View File

@ -127,8 +127,16 @@ typedef struct
float percent;
} musicfade_t;
typedef struct snd_format_s
{
unsigned int speed;
unsigned int width;
unsigned int channels;
} snd_format_t;
typedef struct
{
snd_format_t format;
int samples; // mono samples in buffer
int samplepos; // in mono samples
byte *buffer;
@ -266,6 +274,7 @@ extern convar_t *s_mixahead;
extern convar_t *s_lerping;
extern convar_t *dsp_off;
extern convar_t *s_test; // cvar to testify new effects
extern convar_t *s_samplecount;
void S_InitScaletable( void );
wavdata_t *S_LoadSound( sfx_t *sfx );
@ -303,7 +312,7 @@ void DSP_ClearState( void );
qboolean S_Init( void );
void S_Shutdown( void );
void S_Activate( qboolean active, void *hInst );
void S_Activate( qboolean active );
void S_SoundList_f( void );
void S_SoundInfo_f( void );
@ -356,4 +365,4 @@ void VOX_LoadSound( channel_t *pchan, const char *psz );
float VOX_ModifyPitch( channel_t *ch, float pitch );
int VOX_MixDataToDevice( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset );
#endif//SOUND_H
#endif//SOUND_H

View File

@ -0,0 +1,261 @@
/*
s_backend.c - sound hardware output
Copyright (C) 2009 Uncle Mike
This program 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 3 of the License, or
(at your option) any later version.
This program 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.
*/
#include "common.h"
#if XASH_SOUND == SOUND_SDL
#include "sound.h"
#include <SDL.h>
#define SAMPLE_16BIT_SHIFT 1
#define SECONDARY_BUFFER_SIZE 0x10000
/*
=======================================================================
Global variables. Must be visible to window-procedure function
so it can unlock and free the data block after it has been played.
=======================================================================
*/
static int sdl_dev;
//static qboolean snd_firsttime = true;
//static qboolean primary_format_set;
void SDL_SoundCallback( void *userdata, Uint8 *stream, int len )
{
int size = dma.samples << 1;
int pos = dma.samplepos << 1;
int wrapped = pos + len - size;
if( wrapped < 0 )
{
memcpy( stream, dma.buffer + pos, len );
dma.samplepos += len >> 1;
}
else
{
int remaining = size - pos;
memcpy( stream, dma.buffer + pos, remaining );
memcpy( stream + remaining, dma.buffer, wrapped );
dma.samplepos = wrapped >> 1;
}
}
/*
==================
SNDDMA_Init
Try to find a sound device to mix for.
Returns false if nothing is found.
==================
*/
qboolean SNDDMA_Init( void *hInst )
{
SDL_AudioSpec desired, obtained;
int samplecount;
if( SDL_Init( SDL_INIT_AUDIO ) )
{
MsgDev( D_ERROR, "Audio: SDL: %s \n", SDL_GetError( ) );
return false;
}
#ifdef __linux__
setenv( "PULSE_PROP_application.name", GI->title, 1 );
setenv( "PULSE_PROP_media.role", "game", 1 );
#endif
memset( &desired, 0, sizeof( desired ) );
desired.freq = SOUND_DMA_SPEED;
desired.format = AUDIO_S16LSB;
desired.samples = 1024;
desired.channels = 2;
desired.callback = SDL_SoundCallback;
sdl_dev = SDL_OpenAudioDevice( NULL, 0, &desired, &obtained, 0 );
if( !sdl_dev )
{
Con_Printf( "Couldn't open SDL audio: %s\n", SDL_GetError( ) );
return false;
}
if( obtained.format != AUDIO_S16LSB )
{
Con_Printf( "SDL audio format %d unsupported.\n", obtained.format );
goto fail;
}
if( obtained.channels != 1 && obtained.channels != 2 )
{
Con_Printf( "SDL audio channels %d unsupported.\n", obtained.channels );
goto fail;
}
dma.format.speed = obtained.freq;
dma.format.channels = obtained.channels;
dma.format.width = 2;
samplecount = s_samplecount->value;
if( !samplecount )
samplecount = 0x8000;
dma.samples = samplecount * obtained.channels;
dma.buffer = Z_Malloc( dma.samples * 2 );
dma.samplepos = 0;
Con_Printf( "Using SDL audio driver: %s @ %d Hz\n", SDL_GetCurrentAudioDriver( ), obtained.freq );
SDL_PauseAudioDevice( sdl_dev, 0 );
dma.initialized = true;
return true;
fail:
SNDDMA_Shutdown( );
return false;
}
/*
==============
SNDDMA_GetDMAPos
return the current sample position (in mono samples read)
inside the recirculating dma buffer, so the mixing code will know
how many sample are required to fill it up.
===============
*/
int SNDDMA_GetDMAPos( void )
{
return dma.samplepos;
}
/*
==============
SNDDMA_GetSoundtime
update global soundtime
===============
*/
int SNDDMA_GetSoundtime( void )
{
static int buffers, oldsamplepos;
int samplepos, fullsamples;
fullsamples = dma.samples / 2;
// it is possible to miscount buffers
// if it has wrapped twice between
// calls to S_Update. Oh well.
samplepos = SNDDMA_GetDMAPos( );
if( samplepos < oldsamplepos )
{
buffers++; // buffer wrapped
if( paintedtime > 0x40000000 )
{
// time to chop things off to avoid 32 bit limits
buffers = 0;
paintedtime = fullsamples;
S_StopAllSounds( true );
}
}
oldsamplepos = samplepos;
return ( buffers * fullsamples + samplepos / 2 );
}
/*
==============
SNDDMA_BeginPainting
Makes sure dma.buffer is valid
===============
*/
void SNDDMA_BeginPainting( void )
{
SDL_LockAudio( );
}
/*
==============
SNDDMA_Submit
Send sound to device if buffer isn't really the dma buffer
Also unlocks the dsound buffer
===============
*/
void SNDDMA_Submit( void )
{
SDL_UnlockAudio( );
}
/*
==============
SNDDMA_Shutdown
Reset the sound device for exiting
===============
*/
void SNDDMA_Shutdown( void )
{
Con_Printf( "Shutting down audio.\n" );
dma.initialized = false;
if( sdl_dev )
{
SDL_PauseAudioDevice( sdl_dev, 1 );
#ifndef __EMSCRIPTEN__
SDL_CloseAudioDevice( sdl_dev );
SDL_CloseAudio( );
#endif
}
#ifndef __EMSCRIPTEN__
if( SDL_WasInit( SDL_INIT_AUDIO ) )
SDL_QuitSubSystem( SDL_INIT_AUDIO );
#endif
if( dma.buffer )
{
Mem_Free( dma.buffer );
dma.buffer = NULL;
}
}
/*
===========
S_PrintDeviceName
===========
*/
void S_PrintDeviceName( void )
{
Msg( "Audio: SDL (driver: %s)\n", SDL_GetCurrentAudioDriver( ) );
}
/*
===========
S_Activate
Called when the main window gains or loses focus.
The window have been destroyed and recreated
between a deactivate and an activate.
===========
*/
void S_Activate( qboolean active )
{
SDL_PauseAudioDevice( sdl_dev, !active );
}
#endif // XASH_SOUND == SOUND_SDL