From e4af84e79a9a828ef90d30caf140a56b60b4c149 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Wed, 13 May 2026 21:19:28 -0400 Subject: [PATCH 1/2] fix audiostream cutouts from openal buffer underrun When a streaming OpenAL source plays through all queued buffers it transitions to AL_STOPPED. Per the spec, queueing more buffers after that does not auto-resume playback -- alSourcePlay must be called again. This is the likely cause of music and briefing voices cutting out. To fix this, check for underrun and call alSourcePlay if needed. Also double MAX_STREAM_BUFFERS from 4 to 8, bumping the margin to ~2s of queued audio. Co-Authored-By: Claude Opus 4.7 (1M context) --- code/sound/audiostr.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/code/sound/audiostr.cpp b/code/sound/audiostr.cpp index 30d9037e0cd..0472981f727 100644 --- a/code/sound/audiostr.cpp +++ b/code/sound/audiostr.cpp @@ -24,7 +24,7 @@ #include "sound/ffmpeg/FFmpegWaveFile.h" #endif -#define MAX_STREAM_BUFFERS 4 +#define MAX_STREAM_BUFFERS 8 // status #define ASF_FREE 0 @@ -600,6 +600,20 @@ bool AudioStream::ServiceBuffer (void) m_bPastLimit = true; } + // Recover from buffer underrun: if the source stopped because the queue drained + // between service ticks, OpenAL will not auto-resume even after WriteWaveData + // queues more buffers. We have to call alSourcePlay again ourselves. + ALint state = AL_PLAYING; + OpenAL_ErrorPrint( alGetSourcei(m_source_id, AL_SOURCE_STATE, &state) ); + if (state == AL_STOPPED && m_fPlaying && !m_bReadingDone) { + ALint queued = 0; + OpenAL_ErrorPrint( alGetSourcei(m_source_id, AL_BUFFERS_QUEUED, &queued) ); + if (queued > 0) { + nprintf(("Sound", "SOUND => Audiostream underrun, restarting playback\n")); + OpenAL_ErrorPrint( alSourcePlay(m_source_id) ); + } + } + if ( PlaybackDone() ) { if ( m_bDestroy_when_faded == true ) { SDL_UnlockMutex( write_lock ); From 1245546bb7a2e06f96a3f36f905aeb507611b7bc Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Wed, 13 May 2026 23:37:00 -0400 Subject: [PATCH 2/2] clang-tidy --- code/sound/audiostr.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/sound/audiostr.cpp b/code/sound/audiostr.cpp index 0472981f727..5db90fa2a51 100644 --- a/code/sound/audiostr.cpp +++ b/code/sound/audiostr.cpp @@ -24,7 +24,7 @@ #include "sound/ffmpeg/FFmpegWaveFile.h" #endif -#define MAX_STREAM_BUFFERS 8 +constexpr size_t MAX_STREAM_BUFFERS = 8; // status #define ASF_FREE 0 @@ -438,7 +438,7 @@ bool AudioStream::WriteWaveData (uint size, uint *num_bytes_written, int service const auto alFormat = openal_get_format(m_fileProps.bytes_per_sample * 8, m_fileProps.num_channels); if ( !service ) { - for (int ib = 0; ib < MAX_STREAM_BUFFERS; ib++) { + for (auto &buffer_id : m_buffer_ids) { num_bytes_read = m_pwavefile->Read(uncompressed_wave_data, m_cbBufSize); // if looping then maybe reset wavefile and keep going @@ -452,8 +452,8 @@ bool AudioStream::WriteWaveData (uint size, uint *num_bytes_written, int service m_bReadingDone = 1; break; } else if (num_bytes_read > 0) { - OpenAL_ErrorCheck( alBufferData(m_buffer_ids[ib], alFormat, uncompressed_wave_data, num_bytes_read, m_fileProps.sample_rate), { fRtn = false; goto ErrorExit; } ); - OpenAL_ErrorCheck( alSourceQueueBuffers(m_source_id, 1, &m_buffer_ids[ib]), { fRtn = false; goto ErrorExit; } ); + OpenAL_ErrorCheck( alBufferData(buffer_id, alFormat, uncompressed_wave_data, num_bytes_read, m_fileProps.sample_rate), { fRtn = false; goto ErrorExit; } ); + OpenAL_ErrorCheck( alSourceQueueBuffers(m_source_id, 1, &buffer_id), { fRtn = false; goto ErrorExit; } ); *num_bytes_written += num_bytes_read; } @@ -511,7 +511,7 @@ uint AudioStream::GetMaxWriteSize (void) OpenAL_ErrorCheck( alGetSourcei(m_source_id, AL_BUFFERS_QUEUED, &q), return 0 ); - if (!n && (q >= MAX_STREAM_BUFFERS)) //all buffers queued + if (!n && (q >= sz2i(MAX_STREAM_BUFFERS))) //all buffers queued dwMaxSize = 0; // nprintf(("Alan","Max write size: %d\n", dwMaxSize));