diff --git a/src/audio/SDL_audio.c b/src/audio/SDL_audio.c index 58f287706..9328241f5 100644 --- a/src/audio/SDL_audio.c +++ b/src/audio/SDL_audio.c @@ -677,6 +677,38 @@ void SDL_AudioThreadFinalize(SDL_AudioDevice *device) SDL_AtomicSet(&device->thread_alive, 0); } +typedef enum MixStrategy +{ + MIXSTRATEGY_SILENCE, // just send silence to the device immediately. + MIXSTRATEGY_COPYONE, // Only one thing, so copy to buffer directly without extra steps + // there's probably room for a "mix but don't convert to float first to avoid clipping" strategy, here. + MIXSTRATEGY_MIX // initialize work buffer, mix all logical devices into it, send final mix to physical device's buffer. + //WRITEME MIXSTRATEGY_EACHMIX // The whole shebang: do the work buffer for _each logical device_ for postmix callbacks, then mix together. +} MixStrategy; + + +static MixStrategy ChooseMixStrategy(const SDL_AudioDevice *device) +{ + SDL_LogicalAudioDevice *logdev = device->logical_devices; + if (logdev == NULL) { // uh..._nothing_ to mix? Memset to silence. + return MIXSTRATEGY_SILENCE; + } + + if (logdev->next == NULL) { // only one logical device? + if (logdev->bound_streams == NULL) { // ...with no streams? Silence. + return MIXSTRATEGY_SILENCE; + } else if (SDL_AtomicGet(&logdev->paused)) { // only device is paused? Silence. + return MIXSTRATEGY_SILENCE; + } else if (logdev->bound_streams->next_binding == NULL) { // ...with only one stream? Copy. + return MIXSTRATEGY_COPYONE; + } + } + + return MIXSTRATEGY_MIX; +} + + + // Output device thread. This is split into chunks, so backends that need to control this directly can use the pieces they need without duplicating effort. void SDL_OutputAudioThreadSetup(SDL_AudioDevice *device) @@ -703,31 +735,58 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device) retval = SDL_FALSE; } else { SDL_assert(buffer_size <= device->buffer_size); // you can ask for less, but not more. - SDL_memset(mix_buffer, device->silence_value, buffer_size); // start with silence. - for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { - if (SDL_AtomicGet(&logdev->paused)) { - continue; // paused? Skip this logical device. + switch (ChooseMixStrategy(device)) { + case MIXSTRATEGY_SILENCE: { + //SDL_Log("MIX STRATEGY: SILENCE"); + SDL_memset(mix_buffer, device->silence_value, buffer_size); + break; } - for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { - /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams - for iterating here because the binding linked list can only change while the device lock is held. - (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind - the same stream to different devices at the same time, though.) */ - const int br = SDL_GetAudioStreamData(stream, device->work_buffer, buffer_size); - if (br < 0) { - // oh crud, we probably ran out of memory. This is possibly an overreaction to kill the audio device, but it's likely the whole thing is going down in a moment anyhow. + case MIXSTRATEGY_COPYONE: { + //SDL_Log("MIX STRATEGY: COPYONE"); + SDL_assert(device->logical_devices != NULL); + SDL_assert(device->logical_devices->next == NULL); + SDL_assert(device->logical_devices->bound_streams != NULL); + SDL_assert(device->logical_devices->bound_streams->next_binding == NULL); + const int br = SDL_GetAudioStreamData(device->logical_devices->bound_streams, mix_buffer, buffer_size); + if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow. retval = SDL_FALSE; - break; - } else if (br > 0) { // it's okay if we get less than requested, we mix what we have. - // !!! FIXME: this needs to mix to float32 or int32, so we don't clip. - if (SDL_MixAudioFormat(mix_buffer, device->work_buffer, device->spec.format, br, SDL_MIX_MAXVOLUME) < 0) { // !!! FIXME: allow streams to specify gain? - SDL_assert(!"We probably ended up with some totally unexpected audio format here"); - retval = SDL_FALSE; // uh...? - break; + SDL_memset(mix_buffer, device->silence_value, buffer_size); // just supply silence to the device before we die. + } else if (br < buffer_size) { + SDL_memset(mix_buffer + br, device->silence_value, buffer_size - br); // silence whatever we didn't write to. + } + break; + } + + case MIXSTRATEGY_MIX: { + //SDL_Log("MIX STRATEGY: MIX"); + SDL_memset(mix_buffer, device->silence_value, buffer_size); // start with silence. + for (SDL_LogicalAudioDevice *logdev = device->logical_devices; logdev != NULL; logdev = logdev->next) { + if (SDL_AtomicGet(&logdev->paused)) { + continue; // paused? Skip this logical device. + } + + for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { + /* this will hold a lock on `stream` while getting. We don't explicitly lock the streams + for iterating here because the binding linked list can only change while the device lock is held. + (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind + the same stream to different devices at the same time, though.) */ + const int br = SDL_GetAudioStreamData(stream, device->work_buffer, buffer_size); + if (br < 0) { // Probably OOM. Kill the audio device; the whole thing is likely dying soon anyhow. + retval = SDL_FALSE; + break; + } else if (br > 0) { // it's okay if we get less than requested, we mix what we have. + // !!! FIXME: this needs to mix to float32 or int32, so we don't clip. + if (SDL_MixAudioFormat(mix_buffer, device->work_buffer, device->spec.format, br, SDL_MIX_MAXVOLUME) < 0) { // !!! FIXME: allow streams to specify gain? + SDL_assert(!"We probably ended up with some totally unexpected audio format here"); + retval = SDL_FALSE; // uh...? + break; + } + } } } + break; } }