diff --git a/thirdparty/rexglue-sdk/include/native/audio/sdl/sdl_audio_driver.h b/thirdparty/rexglue-sdk/include/native/audio/sdl/sdl_audio_driver.h new file mode 100644 index 00000000..87211ac9 --- /dev/null +++ b/thirdparty/rexglue-sdk/include/native/audio/sdl/sdl_audio_driver.h @@ -0,0 +1,62 @@ +// Native audio runtime +// Part of the AC6 Recompilation native foundation + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace rex::audio { +class AudioRuntime; +} + +namespace rex::audio::sdl { + +class SdlAudioDriver : public AudioDriver { + public: + SdlAudioDriver(memory::Memory* memory, AudioRuntime* runtime, size_t client_index); + ~SdlAudioDriver() override; + + bool Initialize() override; + void Shutdown() override; + void SubmitFrame(uint32_t frame_ptr) override; + void SubmitSilenceFrame() override; + AudioDriverTelemetry GetTelemetry() const override; + const char* backend_name() const override { return "sdl"; } + uint32_t queue_low_water_frames() const override { return 2; } + uint32_t queue_target_frames() const override { return 3; } + + private: + static void SDLCALL StreamCallback(void* userdata, SDL_AudioStream* stream, + int additional_amount, int total_amount); + void FillStream(SDL_AudioStream* stream, int bytes_needed); + + AudioRuntime* runtime_{nullptr}; + size_t client_index_{0}; + SDL_AudioStream* stream_{nullptr}; + + std::mutex frames_mutex_{}; + std::queue frames_queued_{}; + std::stack frames_unused_{}; + std::array pending_output_frame_{}; + size_t pending_output_float_count_{0}; + size_t pending_output_float_offset_{0}; + + std::atomic shutting_down_{false}; + std::atomic submitted_frames_{0}; + std::atomic consumed_frames_{0}; + std::atomic underrun_count_{0}; + std::atomic silence_injections_{0}; + std::atomic queued_depth_{0}; + std::atomic peak_queued_depth_{0}; +}; + +} // namespace rex::audio::sdl diff --git a/thirdparty/rexglue-sdk/src/native/audio/CMakeLists.txt b/thirdparty/rexglue-sdk/src/native/audio/CMakeLists.txt index 8571b2f3..3b0926de 100644 --- a/thirdparty/rexglue-sdk/src/native/audio/CMakeLists.txt +++ b/thirdparty/rexglue-sdk/src/native/audio/CMakeLists.txt @@ -1,4 +1,4 @@ -# rexaudio - Native audio subsystem library (WASAPI-only, Windows target) +# rexaudio - Native audio subsystem library add_library(rexaudio STATIC audio_driver.cpp @@ -6,7 +6,6 @@ add_library(rexaudio STATIC audio_runtime.cpp audio_system.cpp audio_trace.cpp - wasapi/wasapi_audio_driver.cpp xma/context.cpp xma/decoder.cpp xma/register_file.cpp @@ -14,11 +13,27 @@ add_library(rexaudio STATIC ) add_library(rex::audio ALIAS rexaudio) +if(WIN32) + target_sources(rexaudio PRIVATE + wasapi/wasapi_audio_driver.cpp + ) +else() + target_sources(rexaudio PRIVATE + sdl/sdl_audio_driver.cpp + ) +endif() + target_include_directories(rexaudio PUBLIC $ $ ) target_link_libraries(rexaudio - PUBLIC rexcore libavcodec libavutil ole32 avrt uuid + PUBLIC rexcore libavcodec libavutil ) + +if(WIN32) + target_link_libraries(rexaudio PUBLIC ole32 avrt uuid) +else() + target_link_libraries(rexaudio PUBLIC SDL3::SDL3) +endif() diff --git a/thirdparty/rexglue-sdk/src/native/audio/audio_runtime.cpp b/thirdparty/rexglue-sdk/src/native/audio/audio_runtime.cpp index 4b943574..9962287d 100644 --- a/thirdparty/rexglue-sdk/src/native/audio/audio_runtime.cpp +++ b/thirdparty/rexglue-sdk/src/native/audio/audio_runtime.cpp @@ -12,14 +12,23 @@ #include +#if defined(_WIN32) #include +#else +#include +#endif #include #include #include #include +#if defined(_WIN32) REXCVAR_DEFINE_STRING(audio_backend, "wasapi", "Audio", "Audio backend: wasapi") - .allowed({"wasapi"}) + .allowed({"wasapi", "sdl"}) +#else +REXCVAR_DEFINE_STRING(audio_backend, "sdl", "Audio", "Audio backend: sdl") + .allowed({"sdl"}) +#endif .lifecycle(rex::cvar::Lifecycle::kRequiresRestart); REXCVAR_DEFINE_INT32(audio_max_queue_depth, 8, "Audio", "Maximum queued render-driver frames per client"); @@ -41,7 +50,14 @@ namespace { std::unique_ptr CreateConfiguredDriver(memory::Memory* memory, AudioRuntime* runtime, const size_t client_index) { +#if defined(_WIN32) + if (REXCVAR_GET(audio_backend) == "sdl") { + return std::make_unique(memory, runtime, client_index); + } return std::make_unique(memory, runtime, client_index); +#else + return std::make_unique(memory, runtime, client_index); +#endif } uint32_t QueueDepthLimit() { diff --git a/thirdparty/rexglue-sdk/src/native/audio/sdl/sdl_audio_driver.cpp b/thirdparty/rexglue-sdk/src/native/audio/sdl/sdl_audio_driver.cpp new file mode 100644 index 00000000..d31bc0ef --- /dev/null +++ b/thirdparty/rexglue-sdk/src/native/audio/sdl/sdl_audio_driver.cpp @@ -0,0 +1,283 @@ +/** + ****************************************************************************** + * ReXGlue native SDL audio driver * + ****************************************************************************** + */ + +#include + +#include +#include + +#include + +#include +#include +#include +#include + +REXCVAR_DECLARE(bool, audio_mute); +REXCVAR_DECLARE(bool, audio_trace_render_driver_verbose); + +namespace rex::audio::sdl { + +namespace { + +constexpr int kOutputChannels = 2; +constexpr int kOutputFrameBytes = + static_cast(kRenderDriverTicSamplesPerFrame * kOutputChannels * sizeof(float)); + +} // namespace + +SdlAudioDriver::SdlAudioDriver(memory::Memory* memory, AudioRuntime* runtime, + const size_t client_index) + : AudioDriver(memory), runtime_(runtime), client_index_(client_index) {} + +SdlAudioDriver::~SdlAudioDriver() { + Shutdown(); +} + +bool SdlAudioDriver::Initialize() { + shutting_down_.store(false, std::memory_order_release); + + if (!SDL_InitSubSystem(SDL_INIT_AUDIO)) { + REXAPU_ERROR("SdlAudioDriver init failed for client {}: SDL_InitSubSystem: {}", + client_index_, SDL_GetError()); + return false; + } + + SDL_AudioSpec spec{}; + spec.format = SDL_AUDIO_F32; + spec.channels = kOutputChannels; + spec.freq = kAudioFrameSampleRate; + + stream_ = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, + &SdlAudioDriver::StreamCallback, this); + if (!stream_) { + REXAPU_ERROR("SdlAudioDriver init failed for client {}: SDL_OpenAudioDeviceStream: {}", + client_index_, SDL_GetError()); + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return false; + } + + if (!SDL_ResumeAudioStreamDevice(stream_)) { + REXAPU_ERROR("SdlAudioDriver init failed for client {}: SDL_ResumeAudioStreamDevice: {}", + client_index_, SDL_GetError()); + SDL_DestroyAudioStream(stream_); + stream_ = nullptr; + SDL_QuitSubSystem(SDL_INIT_AUDIO); + return false; + } + + REXAPU_INFO("SdlAudioDriver initialized: client={} channels={} freq={}", client_index_, + kOutputChannels, kAudioFrameSampleRate); + return true; +} + +void SdlAudioDriver::Shutdown() { + const bool was_shutting_down = shutting_down_.exchange(true, std::memory_order_acq_rel); + if (was_shutting_down) { + return; + } + + if (stream_) { + SDL_DestroyAudioStream(stream_); + stream_ = nullptr; + } + + std::lock_guard guard(frames_mutex_); + while (!frames_unused_.empty()) { + delete[] frames_unused_.top(); + frames_unused_.pop(); + } + while (!frames_queued_.empty()) { + delete[] frames_queued_.front(); + frames_queued_.pop(); + } + pending_output_float_count_ = 0; + pending_output_float_offset_ = 0; + + SDL_QuitSubSystem(SDL_INIT_AUDIO); +} + +void SdlAudioDriver::SubmitFrame(const uint32_t frame_ptr) { + if (shutting_down_.load(std::memory_order_acquire)) { + return; + } + + const auto input_frame = memory_->TranslateVirtual(frame_ptr); + if (!input_frame) { + return; + } + + float* output_frame = nullptr; + { + std::lock_guard guard(frames_mutex_); + if (frames_unused_.empty()) { + output_frame = new float[kAudioFrameTotalSamples]; + } else { + output_frame = frames_unused_.top(); + frames_unused_.pop(); + } + } + + std::memcpy(output_frame, input_frame, sizeof(float) * kAudioFrameTotalSamples); + { + std::lock_guard guard(frames_mutex_); + frames_queued_.push(output_frame); + } + + const uint32_t submitted = submitted_frames_.fetch_add(1, std::memory_order_relaxed) + 1; + const uint32_t queued_depth = queued_depth_.fetch_add(1, std::memory_order_relaxed) + 1; + uint32_t previous_peak = peak_queued_depth_.load(std::memory_order_relaxed); + while (queued_depth > previous_peak && + !peak_queued_depth_.compare_exchange_weak(previous_peak, queued_depth, + std::memory_order_relaxed)) { + } + + if (REXCVAR_GET(audio_trace_render_driver_verbose) && + (submitted <= 24 || (submitted % 60) == 0 || queued_depth <= 1)) { + REXAPU_DEBUG( + "SdlAudioDriver::SubmitFrame frame_ptr={:08X} submitted={} consumed={} queued_depth={} peak={} underruns={}", + frame_ptr, submitted, consumed_frames_.load(std::memory_order_relaxed), queued_depth, + peak_queued_depth_.load(std::memory_order_relaxed), + underrun_count_.load(std::memory_order_relaxed)); + } +} + +void SdlAudioDriver::SubmitSilenceFrame() { + if (shutting_down_.load(std::memory_order_acquire)) { + return; + } + + float* output_frame = nullptr; + { + std::lock_guard guard(frames_mutex_); + if (frames_unused_.empty()) { + output_frame = new float[kAudioFrameTotalSamples]; + } else { + output_frame = frames_unused_.top(); + frames_unused_.pop(); + } + } + + std::fill_n(output_frame, kAudioFrameTotalSamples, 0.0f); + { + std::lock_guard guard(frames_mutex_); + frames_queued_.push(output_frame); + } + + const uint32_t submitted = submitted_frames_.fetch_add(1, std::memory_order_relaxed) + 1; + const uint32_t queued_depth = queued_depth_.fetch_add(1, std::memory_order_relaxed) + 1; + silence_injections_.fetch_add(1, std::memory_order_relaxed); + uint32_t previous_peak = peak_queued_depth_.load(std::memory_order_relaxed); + while (queued_depth > previous_peak && + !peak_queued_depth_.compare_exchange_weak(previous_peak, queued_depth, + std::memory_order_relaxed)) { + } + + if (REXCVAR_GET(audio_trace_render_driver_verbose) && + (submitted <= 24 || (submitted % 60) == 0 || queued_depth <= 1)) { + REXAPU_DEBUG( + "SdlAudioDriver::SubmitSilenceFrame submitted={} consumed={} queued_depth={} peak={} underruns={} silence_injections={}", + submitted, consumed_frames_.load(std::memory_order_relaxed), queued_depth, + peak_queued_depth_.load(std::memory_order_relaxed), + underrun_count_.load(std::memory_order_relaxed), + silence_injections_.load(std::memory_order_relaxed)); + } +} + +AudioDriverTelemetry SdlAudioDriver::GetTelemetry() const { + return AudioDriverTelemetry{ + submitted_frames_.load(std::memory_order_relaxed), + consumed_frames_.load(std::memory_order_relaxed), + underrun_count_.load(std::memory_order_relaxed), + silence_injections_.load(std::memory_order_relaxed), + queued_depth_.load(std::memory_order_relaxed), + peak_queued_depth_.load(std::memory_order_relaxed), + }; +} + +void SDLCALL SdlAudioDriver::StreamCallback(void* userdata, SDL_AudioStream* stream, + const int additional_amount, const int total_amount) { + (void)total_amount; + auto* driver = static_cast(userdata); + if (!driver || driver->shutting_down_.load(std::memory_order_acquire) || additional_amount <= 0) { + return; + } + driver->FillStream(stream, additional_amount); +} + +void SdlAudioDriver::FillStream(SDL_AudioStream* stream, int bytes_needed) { + while (bytes_needed > 0 && !shutting_down_.load(std::memory_order_acquire)) { + if (pending_output_float_offset_ == pending_output_float_count_) { + pending_output_float_count_ = 0; + pending_output_float_offset_ = 0; + + float* buffer = nullptr; + { + std::lock_guard guard(frames_mutex_); + if (!frames_queued_.empty()) { + buffer = frames_queued_.front(); + frames_queued_.pop(); + queued_depth_.fetch_sub(1, std::memory_order_relaxed); + } + } + + if (buffer) { + if (!REXCVAR_GET(audio_mute)) { + conversion::sequential_6_BE_to_interleaved_2_LE( + pending_output_frame_.data(), buffer, kRenderDriverTicSamplesPerFrame); + } else { + std::memset(pending_output_frame_.data(), 0, sizeof(float) * pending_output_frame_.size()); + } + pending_output_float_count_ = pending_output_frame_.size(); + { + std::lock_guard guard(frames_mutex_); + frames_unused_.push(buffer); + } + } + } + + if (pending_output_float_count_ == 0) { + std::array silence{}; + if (!SDL_PutAudioStreamData(stream, silence.data(), kOutputFrameBytes)) { + return; + } + ++underrun_count_; + ++silence_injections_; + bytes_needed -= std::min(bytes_needed, kOutputFrameBytes); + continue; + } + + const size_t pending_bytes = + (pending_output_float_count_ - pending_output_float_offset_) * sizeof(float); + const int write_bytes = static_cast( + std::min(pending_bytes, static_cast(std::max(bytes_needed, 0)))); + if (write_bytes <= 0) { + break; + } + + const auto* write_ptr = reinterpret_cast(pending_output_frame_.data()) + + (pending_output_float_offset_ * sizeof(float)); + if (!SDL_PutAudioStreamData(stream, write_ptr, write_bytes)) { + return; + } + + pending_output_float_offset_ += static_cast(write_bytes) / sizeof(float); + bytes_needed -= write_bytes; + + if (pending_output_float_offset_ == pending_output_float_count_) { + pending_output_float_count_ = 0; + pending_output_float_offset_ = 0; + consumed_frames_.fetch_add(1, std::memory_order_relaxed); + if (runtime_) { + runtime_->ReportSamplesConsumedForClient(client_index_, kRenderDriverTicSamplesPerFrame); + runtime_->ConsumeQueuedFramesForClient(client_index_, 1); + runtime_->WakeWorker(); + } + } + } +} + +} // namespace rex::audio::sdl