audio: Remove ChooseMixStrategy.

This is adds complexity and fragility for small optimization wins.

The biggest win is the extremely common case of a single stream providing
the only output, so we'll check for that and skip silencing/mixing/converting.

Otherwise, just use a single mixer path.
This commit is contained in:
Ryan C. Gordon
2023-09-06 10:02:32 -04:00
parent b00cbd76aa
commit 38c8fc05c5

View File

@@ -677,37 +677,6 @@ void SDL_AudioThreadFinalize(SDL_AudioDevice *device)
SDL_AtomicSet(&device->thread_alive, 0); 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. // 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.
@@ -736,21 +705,15 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device)
} else { } else {
SDL_assert(buffer_size <= device->buffer_size); // you can ask for less, but not more. SDL_assert(buffer_size <= device->buffer_size); // you can ask for less, but not more.
switch (ChooseMixStrategy(device)) { // can we do a basic copy without silencing/mixing the buffer? This is an extremely likely scenario, so we special-case it.
case MIXSTRATEGY_SILENCE: { const SDL_bool simple_copy = device->logical_devices && // there's a logical device
//SDL_Log("MIX STRATEGY: SILENCE"); !device->logical_devices->next && // there's only _ONE_ logical device
SDL_memset(device_buffer, device->silence_value, buffer_size); !SDL_AtomicGet(&device->logical_devices->paused) && // it isn't paused
break; device->logical_devices->bound_streams && // there's a bound stream
} !device->logical_devices->bound_streams->next_binding; // there's only _ONE_ bound stream.
case MIXSTRATEGY_COPYONE: { if (simple_copy) {
//SDL_Log("MIX STRATEGY: COPYONE");
SDL_LogicalAudioDevice *logdev = device->logical_devices; SDL_LogicalAudioDevice *logdev = device->logical_devices;
SDL_assert(logdev != NULL);
SDL_assert(logdev->next == NULL);
SDL_assert(logdev->bound_streams != NULL);
SDL_assert(logdev->bound_streams->next_binding == NULL);
SDL_AudioStream *stream = logdev->bound_streams; SDL_AudioStream *stream = logdev->bound_streams;
SDL_SetAudioStreamFormat(stream, NULL, &device->spec); SDL_SetAudioStreamFormat(stream, NULL, &device->spec);
const int br = SDL_GetAudioStreamData(stream, device_buffer, buffer_size); const int br = SDL_GetAudioStreamData(stream, device_buffer, buffer_size);
@@ -760,11 +723,7 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device)
} else if (br < buffer_size) { } else if (br < buffer_size) {
SDL_memset(device_buffer + br, device->silence_value, buffer_size - br); // silence whatever we didn't write to. SDL_memset(device_buffer + br, device->silence_value, buffer_size - br); // silence whatever we didn't write to.
} }
break; } else { // need to actually mix (or silence the buffer)
}
case MIXSTRATEGY_MIX: {
//SDL_Log("MIX STRATEGY: MIX");
float *mix_buffer = (float *) ((device->spec.format == SDL_AUDIO_F32) ? device_buffer : device->mix_buffer); float *mix_buffer = (float *) ((device->spec.format == SDL_AUDIO_F32) ? device_buffer : device->mix_buffer);
const int needed_samples = buffer_size / SDL_AUDIO_BYTESIZE(device->spec.format); const int needed_samples = buffer_size / SDL_AUDIO_BYTESIZE(device->spec.format);
const int work_buffer_size = needed_samples * sizeof (float); const int work_buffer_size = needed_samples * sizeof (float);
@@ -775,7 +734,6 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device)
outspec.format = SDL_AUDIO_F32; outspec.format = SDL_AUDIO_F32;
outspec.channels = device->spec.channels; outspec.channels = device->spec.channels;
outspec.freq = device->spec.freq; outspec.freq = device->spec.freq;
outspec.format = SDL_AUDIO_F32;
SDL_memset(mix_buffer, '\0', buffer_size); // start with silence. SDL_memset(mix_buffer, '\0', buffer_size); // start with silence.
@@ -786,6 +744,7 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device)
for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) { for (SDL_AudioStream *stream = logdev->bound_streams; stream != NULL; stream = stream->next_binding) {
SDL_SetAudioStreamFormat(stream, NULL, &outspec); SDL_SetAudioStreamFormat(stream, NULL, &outspec);
/* this will hold a lock on `stream` while getting. We don't explicitly lock the streams /* 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. 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 (we _do_ lock the stream during binding/unbinding to make sure that two threads can't try to bind
@@ -810,8 +769,6 @@ SDL_bool SDL_OutputAudioThreadIterate(SDL_AudioDevice *device)
ConvertAudio(needed_samples / device->spec.channels, mix_buffer, SDL_AUDIO_F32, device->spec.channels, device->work_buffer, device->spec.format, device->spec.channels, NULL); ConvertAudio(needed_samples / device->spec.channels, mix_buffer, SDL_AUDIO_F32, device->spec.channels, device->work_buffer, device->spec.format, device->spec.channels, NULL);
SDL_memcpy(device_buffer, device->work_buffer, buffer_size); SDL_memcpy(device_buffer, device->work_buffer, buffer_size);
} }
break;
}
} }
// PlayDevice SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice instead! // PlayDevice SHOULD NOT BLOCK, as we are holding a lock right now. Block in WaitDevice instead!