Files
2026-04-20 06:43:35 +03:00

377 lines
15 KiB
Diff

From 807931c9384a822ad0a54b3fbdec5f7af7a83bc1 Mon Sep 17 00:00:00 2001
From: Tom <1568512+tomcl7@users.noreply.github.com>
Date: Wed, 11 Mar 2026 17:43:12 -0400
Subject: [PATCH] fix(audio): frame queuing, XMA decoder sync completion, and
diagnostics
- Clamp queued_frames to 4-64 range, null validation in RegisterRenderDriverClient
- XMA work completion event, WriteRegister blocks until contexts finish
- XMABlockWhileInUse checks work_buffer_ptr to prevent hangs
---
include/rex/audio/audio_system.h | 5 +--
include/rex/audio/xma/context.h | 13 +++++++
src/audio/audio_system.cpp | 44 +++++++++++++++++++---
src/audio/sdl/sdl_audio_driver.cpp | 12 ++++++
src/audio/xma_context.cpp | 3 +-
src/audio/xma_decoder.cpp | 35 +++++++++--------
src/kernel/xboxkrnl/xboxkrnl_audio.cpp | 17 +++++++++
src/kernel/xboxkrnl/xboxkrnl_audio_xma.cpp | 4 ++
8 files changed, 106 insertions(+), 27 deletions(-)
diff --git a/include/native/audio/audio_system.h b/include/native/audio/audio_system.h
index cf67c344..6c6a72fb 100644
--- a/include/native/audio/audio_system.h
+++ b/include/native/audio/audio_system.h
@@ -66,13 +66,12 @@ class AudioSystem : public system::IAudioSystem {
AudioDriver** out_driver) = 0;
virtual void DestroyDriver(AudioDriver* driver) = 0;
- // TODO(gibbed): respect XAUDIO2_MAX_QUEUED_BUFFERS somehow (ie min(64,
- // XAUDIO2_MAX_QUEUED_BUFFERS))
- // static const size_t kMaximumQueuedFrames = 64;
+ static constexpr size_t kMaximumQueuedFrames = 64;
memory::Memory* memory_ = nullptr;
runtime::Processor* processor_ = nullptr;
std::unique_ptr<XmaDecoder> xma_decoder_;
+ uint32_t queued_frames_;
std::atomic<bool> worker_running_ = {false};
system::object_ref<system::XHostThread> worker_thread_;
diff --git a/include/native/audio/xma/context.h b/include/native/audio/xma/context.h
index 75581831..47db366e 100644
--- a/include/native/audio/xma/context.h
+++ b/include/native/audio/xma/context.h
@@ -19,6 +19,7 @@
#include <rex/kernel.h>
#include <rex/memory.h>
+#include <rex/thread.h>
// XMA audio format:
// From research, XMA appears to be based on WMA Pro with
@@ -165,6 +166,17 @@ class XmaContext {
void set_is_allocated(bool is_allocated) { is_allocated_ = is_allocated; }
void set_is_enabled(bool is_enabled) { is_enabled_ = is_enabled; }
+ void SignalWorkDone() {
+ if (work_completion_event_) {
+ work_completion_event_->Set();
+ }
+ }
+ void WaitForWorkDone() {
+ if (work_completion_event_) {
+ rex::thread::Wait(work_completion_event_.get(), false);
+ }
+ }
+
private:
static void SwapInputBuffer(XMA_CONTEXT_DATA* data);
static bool TrySetupNextLoop(XMA_CONTEXT_DATA* data, bool ignore_input_buffer_offset);
@@ -188,6 +200,7 @@ class XmaContext {
int PrepareDecoder(uint8_t* packet, int sample_rate, bool is_two_channel);
memory::Memory* memory_ = nullptr;
+ std::unique_ptr<rex::thread::Event> work_completion_event_;
uint32_t id_ = 0;
uint32_t guest_ptr_ = 0;
diff --git a/src/native/audio/audio_system.cpp b/src/native/audio/audio_system.cpp
index 7a5042a6..6b9767c9 100644
--- a/src/native/audio/audio_system.cpp
+++ b/src/native/audio/audio_system.cpp
@@ -24,7 +24,9 @@
#include <rex/thread.h>
#include <rex/cvar.h>
-REXCVAR_DEFINE_INT32(audio_maxqframes, 64, "Audio", "Adjust audio maximum queued frames");
+REXCVAR_DEFINE_INT32(
+ audio_maxqframes, 8, "Audio",
+ "Max buffered audio frames (range 4-64). Lower reduces latency but may cause stuttering.");
// As with normal Microsoft, there are like twelve different ways to access
// the audio APIs. Early games use XMA*() methods almost exclusively to touch
@@ -44,8 +46,12 @@ AudioSystem::AudioSystem(runtime::Processor* processor)
: memory_(processor->memory()), processor_(processor), worker_running_(false) {
std::memset(clients_, 0, sizeof(clients_));
+ queued_frames_ = std::min(
+ static_cast<uint32_t>(kMaximumQueuedFrames),
+ std::max(static_cast<uint32_t>(REXCVAR_GET(audio_maxqframes)), static_cast<uint32_t>(4)));
+
for (size_t i = 0; i < kMaximumClientCount; ++i) {
- client_semaphores_[i] = rex::thread::Semaphore::Create(0, REXCVAR_GET(audio_maxqframes));
+ client_semaphores_[i] = rex::thread::Semaphore::Create(0, queued_frames_);
assert_not_null(client_semaphores_[i]);
wait_handles_[i] = client_semaphores_[i].get();
}
@@ -89,6 +95,7 @@ void AudioSystem::WorkerThreadMain() {
Initialize();
// Main run loop.
+ uint32_t diag_pump_count = 0;
while (worker_running_) {
// These handles signify the number of submitted samples. Once we reach
// 64 samples, we wait until our audio backend releases a semaphore
@@ -96,10 +103,16 @@ void AudioSystem::WorkerThreadMain() {
auto result = rex::thread::WaitAny(wait_handles_, rex::countof(wait_handles_), true,
std::chrono::milliseconds(500));
if (result.first == rex::thread::WaitResult::kFailed) {
- // TODO: Assert?
+ REXAPU_WARN("AudioWorker: WaitAny failed");
continue;
}
+ if (result.first == rex::thread::WaitResult::kTimeout) {
+ if (diag_pump_count < 5) {
+ REXAPU_DEBUG("AudioWorker: WaitAny timed out (no semaphore signals)");
+ }
+ }
+
if (result.first == thread::WaitResult::kSuccess && result.second == kMaximumClientCount) {
// Shutdown event signaled.
if (paused_) {
@@ -121,10 +134,20 @@ void AudioSystem::WorkerThreadMain() {
global_lock.unlock();
if (client_callback) {
+ if (diag_pump_count < 10) {
+ REXAPU_DEBUG("AudioWorker: dispatching callback {:08X} with arg {:08X} for client {}",
+ client_callback, client_callback_arg, index);
+ }
SCOPE_profile_cpu_i("apu", "rex::audio::AudioSystem->client_callback");
uint64_t args[] = {client_callback_arg};
processor_->Execute(worker_thread_->thread_state(), client_callback, args,
rex::countof(args));
+ if (diag_pump_count < 10) {
+ REXAPU_DEBUG("AudioWorker: callback returned for client {}", index);
+ }
+ diag_pump_count++;
+ } else {
+ REXAPU_DEBUG("AudioWorker: semaphore signaled for client {} but callback is 0", index);
}
pumped = true;
@@ -188,13 +211,17 @@ void AudioSystem::Shutdown() {
}
X_STATUS AudioSystem::RegisterClient(uint32_t callback, uint32_t callback_arg, size_t* out_index) {
+ REXAPU_DEBUG("AudioSystem::RegisterClient: callback={:08X} callback_arg={:08X}", callback,
+ callback_arg);
auto global_lock = global_critical_region_.Acquire();
auto index = FindFreeClient();
assert_true(index >= 0);
+ REXAPU_DEBUG("AudioSystem::RegisterClient: using client index={} queued_frames={}", index,
+ queued_frames_);
auto client_semaphore = client_semaphores_[index].get();
- auto ret = client_semaphore->Release(REXCVAR_GET(audio_maxqframes), nullptr);
+ auto ret = client_semaphore->Release(queued_frames_, nullptr);
assert_true(ret);
AudioDriver* driver;
@@ -219,6 +246,13 @@ X_STATUS AudioSystem::RegisterClient(uint32_t callback, uint32_t callback_arg, s
void AudioSystem::SubmitFrame(size_t index, uint32_t samples_ptr) {
SCOPE_profile_cpu_f("apu");
+ static uint32_t submit_count = 0;
+ if (submit_count < 10) {
+ REXAPU_DEBUG("AudioSystem::SubmitFrame called: index={} samples_ptr={:08X}", index,
+ samples_ptr);
+ submit_count++;
+ }
+
auto global_lock = global_critical_region_.Acquire();
assert_true(index < kMaximumClientCount);
assert_true(clients_[index].driver != NULL);
@@ -296,7 +330,7 @@ bool AudioSystem::Restore(stream::ByteStream* stream) {
client.in_use = true;
auto client_semaphore = client_semaphores_[id].get();
- auto ret = client_semaphore->Release(REXCVAR_GET(audio_maxqframes), nullptr);
+ auto ret = client_semaphore->Release(queued_frames_, nullptr);
assert_true(ret);
AudioDriver* driver = nullptr;
diff --git a/src/native/audio/sdl/sdl_audio_driver.cpp b/src/native/audio/sdl/sdl_audio_driver.cpp
index 554c03bc..bd56f36c 100644
--- a/src/native/audio/sdl/sdl_audio_driver.cpp
+++ b/src/native/audio/sdl/sdl_audio_driver.cpp
@@ -110,6 +110,13 @@ void SDLAudioDriver::SubmitFrame(uint32_t frame_ptr) {
std::memcpy(output_frame, input_frame, frame_samples_ * sizeof(float));
+ static uint32_t sdl_submit_count = 0;
+ if (sdl_submit_count < 10) {
+ REXAPU_DEBUG("SDLAudioDriver::SubmitFrame: frame_ptr={:08X} queued_count={}", frame_ptr,
+ frames_queued_.size() + 1);
+ sdl_submit_count++;
+ }
+
{
std::unique_lock<std::mutex> guard(frames_mutex_);
frames_queued_.push(output_frame);
@@ -146,8 +153,13 @@ void SDLAudioDriver::SDLCallback(void* userdata, Uint8* stream, int len) {
assert_true(len ==
static_cast<int>(sizeof(float) * channel_samples_ * driver->sdl_device_channels_));
+ static uint32_t sdl_callback_count = 0;
std::unique_lock<std::mutex> guard(driver->frames_mutex_);
if (driver->frames_queued_.empty()) {
+ if (sdl_callback_count < 10) {
+ REXAPU_DEBUG("SDLCallback: no frames queued (silence)");
+ sdl_callback_count++;
+ }
std::memset(stream, 0, len);
} else {
auto buffer = driver->frames_queued_.front();
diff --git a/src/native/audio/xma/context.cpp b/src/native/audio/xma/context.cpp
index 76e589e9..1b96d707 100644
--- a/src/native/audio/xma/context.cpp
+++ b/src/native/audio/xma/context.cpp
@@ -39,7 +39,8 @@ namespace rex::audio {
using stream::BitStream;
-XmaContext::XmaContext() = default;
+XmaContext::XmaContext()
+ : work_completion_event_(rex::thread::Event::CreateAutoResetEvent(false)) {}
XmaContext::~XmaContext() {
if (av_context_) {
diff --git a/src/native/audio/xma/decoder.cpp b/src/native/audio/xma/decoder.cpp
index f228692f..7780bac0 100644
--- a/src/native/audio/xma/decoder.cpp
+++ b/src/native/audio/xma/decoder.cpp
@@ -137,18 +137,16 @@ X_STATUS XmaDecoder::Setup(system::KernelState* kernel_state) {
}
void XmaDecoder::WorkerThreadMain() {
- uint32_t idle_loop_count = 0;
while (worker_running_) {
// Okay, let's loop through XMA contexts to find ones we need to decode!
bool did_work = false;
for (uint32_t n = 0; n < kContextCount && worker_running_; n++) {
XmaContext& context = contexts_[n];
- did_work = context.Work() || did_work;
-
- // TODO: Need thread safety to do this.
- // Probably not too important though.
- // registers_.current_context = n;
- // registers_.next_context = (n + 1) % kContextCount;
+ bool worked = context.Work();
+ if (worked) {
+ context.SignalWorkDone();
+ }
+ did_work = did_work || worked;
}
if (paused_) {
@@ -156,18 +154,11 @@ void XmaDecoder::WorkerThreadMain() {
resume_fence_.Wait();
}
- if (!did_work) {
- idle_loop_count++;
- } else {
- idle_loop_count = 0;
- }
-
- if (idle_loop_count > 500) {
- // Idle for an extended period. Introduce a 20ms wait.
- rex::thread::Wait(work_event_.get(), false, std::chrono::milliseconds(20));
+ if (did_work) {
+ continue;
}
-
- rex::thread::MaybeYield();
+ // No work done this iteration, block until signaled.
+ rex::thread::Wait(work_event_.get(), false);
}
}
@@ -292,6 +283,7 @@ void XmaDecoder::WriteRegister(uint32_t addr, uint32_t value) {
// The context ID is a bit in the range of the entire context array.
uint32_t base_context_id = (r - XmaRegister::Context0Kick) * 32;
+ uint32_t kicked_value = value;
for (int i = 0; value && i < 32; ++i, value >>= 1) {
if (value & 1) {
uint32_t context_id = base_context_id + i;
@@ -301,6 +293,13 @@ void XmaDecoder::WriteRegister(uint32_t addr, uint32_t value) {
}
// Signal the decoder thread to start processing.
work_event_->Set();
+ // Block until the worker finishes, so the game sees updated context data.
+ for (int i = 0; kicked_value && i < 32; ++i, kicked_value >>= 1) {
+ if (kicked_value & 1) {
+ uint32_t context_id = base_context_id + i;
+ contexts_[context_id].WaitForWorkDone();
+ }
+ }
} else if (r >= XmaRegister::Context0Lock && r <= XmaRegister::Context9Lock) {
// Context lock command.
// This requests a lock by flagging the context.
diff --git a/src/kernel/xboxkrnl/xboxkrnl_audio.cpp b/src/kernel/xboxkrnl/xboxkrnl_audio.cpp
index 31407f26..dfd9db81 100644
--- a/src/kernel/xboxkrnl/xboxkrnl_audio.cpp
+++ b/src/kernel/xboxkrnl/xboxkrnl_audio.cpp
@@ -54,7 +54,17 @@ ppc_u32_result_t XAudioEnableDucker_entry(ppc_u32_t unk) {
ppc_u32_result_t XAudioRegisterRenderDriverClient_entry(ppc_pu32_t callback_ptr,
ppc_pu32_t driver_ptr) {
+ REXKRNL_DEBUG("XAudioRegisterRenderDriverClient called! callback_ptr={:08X} driver_ptr={:08X}",
+ callback_ptr.guest_address(), driver_ptr.guest_address());
+ if (!callback_ptr) {
+ return X_E_INVALIDARG;
+ }
+
uint32_t callback = callback_ptr[0];
+
+ if (!callback) {
+ return X_E_INVALIDARG;
+ }
uint32_t callback_arg = callback_ptr[1];
auto* audio_system = static_cast<audio::AudioSystem*>(kernel_state()->emulator()->audio_system());
@@ -82,6 +92,13 @@ ppc_u32_result_t XAudioSubmitRenderDriverFrame_entry(ppc_pvoid_t driver_ptr,
ppc_pvoid_t samples_ptr) {
assert_true((driver_ptr.guest_address() & 0xFFFF0000) == 0x41550000);
+ static uint32_t submit_krnl_count = 0;
+ if (submit_krnl_count < 10) {
+ REXKRNL_DEBUG("XAudioSubmitRenderDriverFrame: driver={:08X} samples={:08X}",
+ driver_ptr.guest_address(), samples_ptr.guest_address());
+ submit_krnl_count++;
+ }
+
auto* audio_system = static_cast<audio::AudioSystem*>(kernel_state()->emulator()->audio_system());
audio_system->SubmitFrame(driver_ptr.guest_address() & 0x0000FFFF, samples_ptr.guest_address());
diff --git a/src/kernel/xboxkrnl/xboxkrnl_audio_xma.cpp b/src/kernel/xboxkrnl/xboxkrnl_audio_xma.cpp
index 17a4b4e1..3102f4fc 100644
--- a/src/kernel/xboxkrnl/xboxkrnl_audio_xma.cpp
+++ b/src/kernel/xboxkrnl/xboxkrnl_audio_xma.cpp
@@ -64,6 +64,7 @@ using rex::audio::XMA_CONTEXT_DATA;
// https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.xaudio2.xaudio2_buffer(v=vs.85).aspx
ppc_u32_result_t XMACreateContext_entry(ppc_pu32_t context_out_ptr) {
+ REXKRNL_DEBUG("XMACreateContext called!");
auto xma_decoder =
static_cast<audio::AudioSystem*>(kernel_state()->emulator()->audio_system())->xma_decoder();
uint32_t context_ptr = xma_decoder->AllocateContext();
@@ -341,6 +342,9 @@ ppc_u32_result_t XMABlockWhileInUse_entry(ppc_pvoid_t context_ptr) {
if (!context.input_buffer_0_valid && !context.input_buffer_1_valid) {
break;
}
+ if (!context.work_buffer_ptr) {
+ break;
+ }
rex::thread::Sleep(std::chrono::milliseconds(1));
} while (true);
return 0;
--
2.52.0.windows.1