From cd631fc44705d752060215d740d2268e29973b07 Mon Sep 17 00:00:00 2001 From: Joel Linn Date: Tue, 8 Jun 2021 02:17:59 +0200 Subject: [PATCH] [APU, SDL] Refactor sample submission - Move sample conversion to SDL callback thread - Add early channel down-conversion --- src/xenia/apu/conversion.h | 53 +++++++++++++ src/xenia/apu/sdl/sdl_audio_driver.cc | 109 +++++++++++++++++--------- src/xenia/apu/sdl/sdl_audio_driver.h | 3 + 3 files changed, 127 insertions(+), 38 deletions(-) create mode 100644 src/xenia/apu/conversion.h diff --git a/src/xenia/apu/conversion.h b/src/xenia/apu/conversion.h new file mode 100644 index 000000000..2a487af78 --- /dev/null +++ b/src/xenia/apu/conversion.h @@ -0,0 +1,53 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_APU_CONVERSION_H_ +#define XENIA_APU_CONVERSION_H_ + +#include + +#include "xenia/base/byte_order.h" + +namespace xe { +namespace apu { +namespace conversion { + +inline void sequential_6_BE_to_interleaved_6_LE(float* output, + const float* input, + size_t ch_sample_count) { + for (size_t sample = 0; sample < ch_sample_count; sample++) { + for (size_t channel = 0; channel < 6; channel++) { + output[sample * 6 + channel] = + xe::byte_swap(input[channel * ch_sample_count + sample]); + } + } +} +inline void sequential_6_BE_to_interleaved_2_LE(float* output, + const float* input, + size_t ch_sample_count) { + // Default 5.1 channel mapping is fl, fr, fc, lf, bl, br + // https://docs.microsoft.com/en-us/windows/win32/xaudio2/xaudio2-default-channel-mapping + for (size_t sample = 0; sample < ch_sample_count; sample++) { + // put center on left and right, discard low frequency + float fl = xe::byte_swap(input[0 * ch_sample_count + sample]); + float fr = xe::byte_swap(input[1 * ch_sample_count + sample]); + float fc = xe::byte_swap(input[2 * ch_sample_count + sample]); + float br = xe::byte_swap(input[4 * ch_sample_count + sample]); + float bl = xe::byte_swap(input[5 * ch_sample_count + sample]); + float center_halved = fc * 0.5f; + output[sample * 2] = (fl + bl + center_halved) * (1.0f / 2.5f); + output[sample * 2 + 1] = (fr + br + center_halved) * (1.0f / 2.5f); + } +} + +} // namespace conversion +} // namespace apu +} // namespace xe + +#endif diff --git a/src/xenia/apu/sdl/sdl_audio_driver.cc b/src/xenia/apu/sdl/sdl_audio_driver.cc index a16e9e5d1..bdda565e1 100644 --- a/src/xenia/apu/sdl/sdl_audio_driver.cc +++ b/src/xenia/apu/sdl/sdl_audio_driver.cc @@ -10,9 +10,13 @@ #include "xenia/apu/sdl/sdl_audio_driver.h" #include +#include #include "xenia/apu/apu_flags.h" +#include "xenia/apu/conversion.h" +#include "xenia/base/assert.h" #include "xenia/base/logging.h" +#include "xenia/base/profiling.h" #include "xenia/helper/sdl/sdl_helper.h" namespace xe { @@ -46,41 +50,37 @@ bool SDLAudioDriver::Initialize() { } sdl_initialized_ = true; - SDL_AudioCallback audio_callback = [](void* userdata, Uint8* stream, - int len) -> void { - assert_true(len == frame_size_); - const auto driver = static_cast(userdata); - - std::unique_lock guard(driver->frames_mutex_); - if (driver->frames_queued_.empty()) { - memset(stream, 0, len); - } else { - auto buffer = driver->frames_queued_.front(); - driver->frames_queued_.pop(); - if (cvars::mute) { - memset(stream, 0, len); - } else { - memcpy(stream, buffer, len); - } - driver->frames_unused_.push(buffer); - - auto ret = driver->semaphore_->Release(1, nullptr); - assert_true(ret); + SDL_AudioSpec desired_spec = {}; + SDL_AudioSpec obtained_spec; + desired_spec.freq = frame_frequency_; + desired_spec.format = AUDIO_F32; + desired_spec.channels = frame_channels_; + desired_spec.samples = channel_samples_; + desired_spec.callback = SDLCallback; + desired_spec.userdata = this; + // Allow the hardware to decide between 5.1 and stereo + int allowed_change = SDL_AUDIO_ALLOW_CHANNELS_CHANGE; + for (int i = 0; i < 2; i++) { + sdl_device_id_ = SDL_OpenAudioDevice(nullptr, 0, &desired_spec, + &obtained_spec, allowed_change); + if (sdl_device_id_ <= 0) { + XELOGE("SDL_OpenAudioDevice() failed."); + return false; } - }; - - SDL_AudioSpec wanted_spec = {}; - wanted_spec.freq = frame_frequency_; - wanted_spec.format = AUDIO_F32; - wanted_spec.channels = frame_channels_; - wanted_spec.samples = channel_samples_; - wanted_spec.callback = audio_callback; - wanted_spec.userdata = this; - sdl_device_id_ = SDL_OpenAudioDevice(nullptr, 0, &wanted_spec, nullptr, 0); + if (obtained_spec.channels == 2 || obtained_spec.channels == 6) { + break; + } + // If the system is 4 or 7.1, let SDL convert + allowed_change = 0; + SDL_CloseAudioDevice(sdl_device_id_); + sdl_device_id_ = -1; + } if (sdl_device_id_ <= 0) { - XELOGE("SDL_OpenAudioDevice() failed."); + XELOGE("Failed to get a compatible SDL Audio Device."); return false; } + sdl_device_channels_ = obtained_spec.channels; + SDL_PauseAudioDevice(sdl_device_id_, 0); return true; @@ -99,13 +99,7 @@ void SDLAudioDriver::SubmitFrame(uint32_t frame_ptr) { } } - // interleave the data - for (size_t index = 0, o = 0; index < channel_samples_; ++index) { - for (size_t channel = 0, table = 0; channel < frame_channels_; - ++channel, table += channel_samples_) { - output_frame[o++] = xe::byte_swap(input_frame[table + index]); - } - } + std::memcpy(output_frame, input_frame, frame_samples_ * sizeof(float)); { std::unique_lock guard(frames_mutex_); @@ -133,6 +127,45 @@ void SDLAudioDriver::Shutdown() { }; } +void SDLAudioDriver::SDLCallback(void* userdata, Uint8* stream, int len) { + SCOPE_profile_cpu_f("apu"); + if (!userdata || !stream) { + XELOGE("SDLAudioDriver::sdl_callback called with nullptr."); + return; + } + const auto driver = static_cast(userdata); + assert_true(len == + sizeof(float) * channel_samples_ * driver->sdl_device_channels_); + + std::unique_lock guard(driver->frames_mutex_); + if (driver->frames_queued_.empty()) { + std::memset(stream, 0, len); + } else { + auto buffer = driver->frames_queued_.front(); + driver->frames_queued_.pop(); + if (cvars::mute) { + std::memset(stream, 0, len); + } else { + switch (driver->sdl_device_channels_) { + case 2: + conversion::sequential_6_BE_to_interleaved_2_LE( + reinterpret_cast(stream), buffer, channel_samples_); + break; + case 6: + conversion::sequential_6_BE_to_interleaved_6_LE( + reinterpret_cast(stream), buffer, channel_samples_); + break; + default: + assert_unhandled_case(driver->sdl_device_channels_); + break; + } + } + driver->frames_unused_.push(buffer); + + auto ret = driver->semaphore_->Release(1, nullptr); + assert_true(ret); + } +}; } // namespace sdl } // namespace apu } // namespace xe diff --git a/src/xenia/apu/sdl/sdl_audio_driver.h b/src/xenia/apu/sdl/sdl_audio_driver.h index cb957105f..3d32b86e0 100644 --- a/src/xenia/apu/sdl/sdl_audio_driver.h +++ b/src/xenia/apu/sdl/sdl_audio_driver.h @@ -32,10 +32,13 @@ class SDLAudioDriver : public AudioDriver { void Shutdown(); protected: + static void SDLCallback(void* userdata, Uint8* stream, int len); + xe::threading::Semaphore* semaphore_ = nullptr; SDL_AudioDeviceID sdl_device_id_ = -1; bool sdl_initialized_ = false; + uint8_t sdl_device_channels_ = 0; static const uint32_t frame_frequency_ = 48000; static const uint32_t frame_channels_ = 6;