/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "audiostreamlinux.h" #include "audio/audiostream.h" #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."); SDL_AudioSpec desired; memoryZero(&desired, sizeof(SDL_AudioSpec)); desired.freq = (int) stream->pcm.sampleRate; desired.format = AUDIO_S16SYS; desired.channels = stream->pcm.channels; desired.samples = AUDIO_LINUX_LEAD_FRAMES; stream->platform.device = SDL_OpenAudioDevice(NULL, 0, &desired, NULL, 0); if(stream->platform.device == 0) { errorThrow("Failed to open SDL2 audio device: %s", SDL_GetError()); } errorOk(); } errorret_t audioStreamLinuxDispose(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); SDL_CloseAudioDevice(stream->platform.device); errorOk(); } errorret_t audioStreamLinuxBuffer(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); uint8_t *mixed = memoryAllocate(stream->dataSize); memoryZero(mixed, stream->dataSize); SDL_MixAudioFormat( mixed, stream->data, AUDIO_S16SYS, (Uint32) stream->dataSize, (stream->volume * SDL_MIX_MAXVOLUME) / 0xFF ); int queued = SDL_QueueAudio( stream->platform.device, mixed, (Uint32) stream->dataSize ); memoryFree(mixed); if(queued != 0) { errorThrow("Failed to queue SDL2 audio data: %s", SDL_GetError()); } SDL_PauseAudioDevice(stream->platform.device, 0); errorOk(); } bool_t audioStreamLinuxIsFinished(audiostream_t *stream) { assertNotNull(stream, "Stream cannot be NULL."); // 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; }