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:
@@ -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!
|
||||||
|
|||||||
Reference in New Issue
Block a user