diff --git a/src/dusk/audio/audiostream.c b/src/dusk/audio/audiostream.c index d1bbc858..c625cd21 100644 --- a/src/dusk/audio/audiostream.c +++ b/src/dusk/audio/audiostream.c @@ -91,13 +91,12 @@ errorret_t audioStreamUpdate(audiostream_t *stream) { // start of the buffer (loopTo is not yet honored) since replaying the // exact same stream->data/dataSize needs no platform-specific changes; // an arbitrary loopTo offset would need slicing that buffer instead. + // Checked in this order (finished-check before needs-buffering) so that a + // loop restart falls straight through into re-buffering within this same + // call, instead of leaving BUFFERED cleared for the caller to notice and + // act on next frame - that extra frame of latency was audible as a gap + // at every loop boundary. if( - (stream->state & AUDIO_STREAM_STATE_PLAYING) && - !(stream->state & AUDIO_STREAM_STATE_BUFFERED) - ) { - errorChain(audioStreamPlatformBuffer(stream)); - stream->state |= AUDIO_STREAM_STATE_BUFFERED; - } else if( (stream->state & AUDIO_STREAM_STATE_PLAYING) && (stream->state & AUDIO_STREAM_STATE_BUFFERED) && audioStreamPlatformIsFinished(stream) @@ -117,6 +116,14 @@ errorret_t audioStreamUpdate(audiostream_t *stream) { } } + if( + (stream->state & AUDIO_STREAM_STATE_PLAYING) && + !(stream->state & AUDIO_STREAM_STATE_BUFFERED) + ) { + errorChain(audioStreamPlatformBuffer(stream)); + stream->state |= AUDIO_STREAM_STATE_BUFFERED; + } + errorOk(); } diff --git a/src/dusklinux/audio/audiostreamlinux.c b/src/dusklinux/audio/audiostreamlinux.c index df0881aa..9f4bcb7c 100644 --- a/src/dusklinux/audio/audiostreamlinux.c +++ b/src/dusklinux/audio/audiostreamlinux.c @@ -10,6 +10,11 @@ #include "assert/assert.h" #include "util/memory.h" +// How many frames of lead time to keep queued ahead of playback. Matches +// SDL_AudioSpec.samples below - the device's own internal buffer size - so +// this is "never let the queue run drier than one SDL-internal buffer." +#define AUDIO_LINUX_LEAD_FRAMES 4096 + errorret_t audioStreamLinuxInit(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); @@ -18,7 +23,7 @@ errorret_t audioStreamLinuxInit(audiostream_t *stream) { desired.freq = (int) stream->pcm.sampleRate; desired.format = AUDIO_S16SYS; desired.channels = stream->pcm.channels; - desired.samples = 4096; + desired.samples = AUDIO_LINUX_LEAD_FRAMES; stream->platform.device = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, 0); if(stream->platform.device == 0) { @@ -63,5 +68,16 @@ errorret_t audioStreamLinuxBuffer(audiostream_t *stream) { bool_t audioStreamLinuxIsFinished(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); - return SDL_GetQueuedAudioSize(stream->platform.device) == 0; + // Reports "finished" (ready to be re-buffered) with AUDIO_LINUX_LEAD_FRAMES + // of margin still queued, rather than waiting for the queue to actually + // run dry. SDL_QueueAudio only ever appends to a FIFO, so queuing the next + // loop this early never causes overlap - it just avoids ever going silent + // while our once-per-frame Update() notices and catches up. Waiting for + // truly empty (as this used to) guarantees a gap by definition: silence + // has already started by the time "empty" can be observed. + const Uint32 leadBytes = (Uint32) ( + AUDIO_LINUX_LEAD_FRAMES * stream->pcm.channels * sizeof(int16_t) + ); + + return SDL_GetQueuedAudioSize(stream->platform.device) <= leadBytes; }