simplify/fix sdl2 sound backend, fixes no audio in CLI

- simplified ring-buffer mechanism
- added proper locking for all variables accessed by 2 different threads
- fixed oob writes that occassionally crashed SDL's "Alsa Hotplug thread"
- make buffer sufficiently large to prebuffer enough samples to survive
  the occassional SDL_Delay(1) in the frontend.
- fixed ignoring volume set by the SPU.
- improved speed and robustness by not calling malloc over and over in
  SDL callback, and copying directly to the SDL buffer if volume is max
  (no need to use mixer to lower the volume in that case).
This commit is contained in:
rofl0r 2021-10-25 16:09:02 +00:00
parent 09506c9a19
commit 9f4e6992fb
1 changed files with 70 additions and 68 deletions

View File

@ -51,10 +51,9 @@ SNDSDLUnMuteAudio,
SNDSDLSetVolume
};
static u16 *stereodata16;
static u32 soundoffset;
static volatile u32 soundpos;
static u32 soundlen;
static u8 *stereodata;
static u8 *mixerdata;
static u32 soundoff;
static u32 soundbufsize;
static SDL_AudioSpec audiofmt;
int audio_volume;
@ -81,20 +80,26 @@ DWORD WINAPI SNDXBOXThread( LPVOID )
static void MixAudio(void *userdata, Uint8 *stream, int len) {
int i;
Uint8 *soundbuf=(Uint8 *)stereodata16;
Uint8 *stream_tmp=(Uint8 *)malloc(len);
for (i = 0; i < len; i++)
{
if (soundpos >= soundbufsize)
soundpos = 0;
stream_tmp[i] = soundbuf[soundpos];
soundpos++;
/* if the volume is max, we don't need to call the mixer and do additional
memory copying/cleaning, but can copy directly into the SDL buffer. */
u8 *dest = audio_volume == SDL_MIX_MAXVOLUME ? stream : mixerdata;
SDL_LockAudio();
if (len > soundoff) {
//dprintf(2, "bu: want %u, got %u\n", (unsigned)len, (unsigned)soundoff);
/* buffer underrun - rather than zeroing out SDL's audio buffer, we just
hand back the existing data, which reduces cracking */
len = soundoff;
}
memcpy(dest, stereodata, len);
soundoff -= len;
/* move rest of prebuffered data to the beginning of the buffer */
if (soundoff)
memmove(stereodata, stereodata+len, soundoff);
SDL_UnlockAudio();
if(audio_volume != SDL_MIX_MAXVOLUME) {
memset(stream, 0, len);
SDL_MixAudio(stream, mixerdata, len, audio_volume);
}
memset(stream, 0, len);
SDL_MixAudio(stream, stream_tmp, len, audio_volume);
free(stream_tmp);
}
//////////////////////////////////////////////////////////////////////////////
@ -114,34 +119,37 @@ int SNDSDLInit(int buffersize)
//samples should be a power of 2 according to SDL-doc
//so normalize it to the nearest power of 2 here
u32 normSamples = 512;
while (normSamples < audiofmt.samples)
normSamples <<= 1;
while (normSamples < audiofmt.samples) normSamples <<= 1;
audiofmt.samples = normSamples;
soundlen = audiofmt.freq / 60; // 60 for NTSC
soundbufsize = buffersize * sizeof(s16) * 2;
if (SDL_OpenAudio(&audiofmt, NULL) != 0)
SDL_AudioSpec obtained;
if (SDL_OpenAudio(&audiofmt, &obtained) != 0)
{
return -1;
}
if ((stereodata16 = (u16 *)malloc(soundbufsize)) == NULL)
audiofmt = obtained;
/* allocate multiple of the size needed by SDL to be able to efficiently pre-buffer.
we effectively need the engine to pre-buffer a couple milliseconds because the
frontend might call SDL_Delay() to achieve the desired framerate, during which
the buffer isn't updated by the SPU. */
soundbufsize = audiofmt.size * 4;
soundoff = 0;
if ((stereodata = (u8*)calloc(soundbufsize, 1)) == NULL ||
(mixerdata = (u8*)calloc(soundbufsize, 1)) == NULL)
return -1;
memset(stereodata16, 0, soundbufsize);
soundpos = 0;
SDL_PauseAudio(0);
#ifdef _XBOX
doterminate = false;
doterminate = false;
terminated = false;
CreateThread(0,0,SNDXBOXThread,0,0,0);
#endif
SDL_PauseAudio(0);
return 0;
}
@ -157,38 +165,22 @@ void SNDSDLDeInit()
#endif
SDL_CloseAudio();
if (stereodata16)
free(stereodata16);
free(stereodata);
free(mixerdata);
stereodata = NULL;
mixerdata = NULL;
}
//////////////////////////////////////////////////////////////////////////////
void SNDSDLUpdateAudio(s16 *buffer, u32 num_samples)
{
u32 copy1size=0, copy2size=0;
// dprintf(2, "up: %u\n", num_samples);
u8 *incoming = (u8*) buffer;
u32 n = num_samples * 4;
SDL_LockAudio();
if ((soundbufsize - soundoffset) < (num_samples * sizeof(s16) * 2))
{
copy1size = (soundbufsize - soundoffset);
copy2size = (num_samples * sizeof(s16) * 2) - copy1size;
}
else
{
copy1size = (num_samples * sizeof(s16) * 2);
copy2size = 0;
}
memcpy((((u8 *)stereodata16)+soundoffset), buffer, copy1size);
// ScspConvert32uto16s((s32 *)leftchanbuffer, (s32 *)rightchanbuffer, (s16 *)(((u8 *)stereodata16)+soundoffset), copy1size / sizeof(s16) / 2);
if (copy2size)
memcpy(stereodata16, ((u8 *)buffer)+copy1size, copy2size);
// ScspConvert32uto16s((s32 *)leftchanbuffer, (s32 *)rightchanbuffer, (s16 *)stereodata16, copy2size / sizeof(s16) / 2);
soundoffset += copy1size + copy2size;
soundoffset %= soundbufsize;
memcpy(stereodata+soundoff, incoming, n);
soundoff += n;
SDL_UnlockAudio();
}
@ -196,14 +188,11 @@ void SNDSDLUpdateAudio(s16 *buffer, u32 num_samples)
u32 SNDSDLGetAudioSpace()
{
u32 freespace=0;
if (soundoffset > soundpos)
freespace = soundbufsize - soundoffset + soundpos;
else
freespace = soundpos - soundoffset;
return (freespace / sizeof(s16) / 2);
u32 tmp;
SDL_LockAudio();
tmp = (soundbufsize - soundoff)/4;
SDL_UnlockAudio();
return tmp;
}
//////////////////////////////////////////////////////////////////////////////
@ -222,16 +211,29 @@ void SNDSDLUnMuteAudio()
//////////////////////////////////////////////////////////////////////////////
/* the engine's min/max is 0/100, SDLs 0/128 */
void SNDSDLSetVolume(int volume)
{
u32 tmp = volume * SDL_MIX_MAXVOLUME;
SDL_LockAudio();
audio_volume = tmp / 100;
SDL_UnlockAudio();
}
//////////////////////////////////////////////////////////////////////////////
/* these 2 are special functions that the GTK frontend calls directly.
and it has a slider where its max is 128. doh. */
int SNDSDLGetAudioVolume()
{
return audio_volume;
SDL_LockAudio();
int tmp = audio_volume;
SDL_UnlockAudio();
return tmp;
}
void SNDSDLSetAudioVolume(int value)
{
audio_volume = value;
void SNDSDLSetAudioVolume(int volume) {
SDL_LockAudio();
audio_volume = volume;
SDL_UnlockAudio();
}