From edbd724370fdbfe9edf9e1cfd7a142cf8a4a39a3 Mon Sep 17 00:00:00 2001 From: gibbed Date: Fri, 19 Jun 2015 21:48:51 -0500 Subject: [PATCH] - Fixed a few bugs with ringbuffer, and some cleanup. - Reworked audio system to use semaphores instead of events for waiting. Should fix rare issues where the XAudio2 driver would run out of buffers even though it was supposed to be guarded against that. --- src/xenia/apu/audio_system.cc | 71 +++++++++++-------- src/xenia/apu/audio_system.h | 16 +++-- src/xenia/apu/xaudio2/xaudio2_audio_driver.cc | 21 ++---- src/xenia/apu/xaudio2/xaudio2_audio_driver.h | 4 +- src/xenia/apu/xaudio2/xaudio2_audio_system.cc | 4 +- src/xenia/apu/xaudio2/xaudio2_audio_system.h | 2 +- src/xenia/base/ring_buffer.cc | 64 +++++++---------- src/xenia/base/ring_buffer.h | 47 +++++++----- 8 files changed, 118 insertions(+), 111 deletions(-) diff --git a/src/xenia/apu/audio_system.cc b/src/xenia/apu/audio_system.cc index d4580991e..659934595 100644 --- a/src/xenia/apu/audio_system.cc +++ b/src/xenia/apu/audio_system.cc @@ -62,18 +62,22 @@ AudioSystem::AudioSystem(Emulator* emulator) worker_running_(false), decoder_running_(false) { std::memset(clients_, 0, sizeof(clients_)); - for (size_t i = 0; i < maximum_client_count_; ++i) { + for (size_t i = 0; i < kMaximumClientCount; ++i) { unused_clients_.push(i); } - for (size_t i = 0; i < xe::countof(client_wait_handles_); ++i) { - client_wait_handles_[i] = CreateEvent(NULL, TRUE, FALSE, NULL); + for (size_t i = 0; i < kMaximumClientCount; ++i) { + client_semaphores_[i] = CreateSemaphore(NULL, 0, kMaximumQueuedFrames, NULL); + wait_handles_[i] = client_semaphores_[i]; } + shutdown_event_ = CreateEvent(NULL, TRUE, FALSE, NULL); + wait_handles_[kMaximumClientCount] = shutdown_event_; } AudioSystem::~AudioSystem() { - for (size_t i = 0; i < xe::countof(client_wait_handles_); ++i) { - CloseHandle(client_wait_handles_[i]); + for (size_t i = 0; i < kMaximumClientCount; ++i) { + CloseHandle(client_semaphores_[i]); } + CloseHandle(shutdown_event_); } void av_log_callback(void *avcl, int level, const char *fmt, va_list va) { @@ -149,16 +153,16 @@ void AudioSystem::WorkerThreadMain() { // Main run loop. while (worker_running_) { auto result = - WaitForMultipleObjectsEx(DWORD(xe::countof(client_wait_handles_)), - client_wait_handles_, FALSE, INFINITE, FALSE); + WaitForMultipleObjectsEx(DWORD(xe::countof(wait_handles_)), + wait_handles_, FALSE, INFINITE, FALSE); if (result == WAIT_FAILED || - result == WAIT_OBJECT_0 + maximum_client_count_) { + result == WAIT_OBJECT_0 + kMaximumClientCount) { continue; } size_t pumped = 0; if (result >= WAIT_OBJECT_0 && - result <= WAIT_OBJECT_0 + (maximum_client_count_ - 1)) { + result <= WAIT_OBJECT_0 + (kMaximumClientCount - 1)) { size_t index = result - WAIT_OBJECT_0; do { lock_.lock(); @@ -174,8 +178,8 @@ void AudioSystem::WorkerThreadMain() { } pumped++; index++; - } while (index < maximum_client_count_ && - WaitForSingleObject(client_wait_handles_[index], 0) == + } while (index < kMaximumClientCount && + WaitForSingleObject(client_semaphores_[index], 0) == WAIT_OBJECT_0); } @@ -225,7 +229,7 @@ void AudioSystem::Initialize() {} void AudioSystem::Shutdown() { worker_running_ = false; - SetEvent(client_wait_handles_[maximum_client_count_]); + SetEvent(shutdown_event_); worker_thread_->Wait(0, 0, 0, nullptr); worker_thread_.reset(); @@ -297,10 +301,10 @@ X_STATUS AudioSystem::RegisterClient(uint32_t callback, uint32_t callback_arg, auto index = unused_clients_.front(); - auto wait_handle = client_wait_handles_[index]; - ResetEvent(wait_handle); + auto client_semaphore = client_semaphores_[index]; + assert_true(ReleaseSemaphore(client_semaphore, kMaximumQueuedFrames, NULL) == TRUE); AudioDriver* driver; - auto result = CreateDriver(index, wait_handle, &driver); + auto result = CreateDriver(index, client_semaphore, &driver); if (XFAILED(result)) { return result; } @@ -324,7 +328,7 @@ void AudioSystem::SubmitFrame(size_t index, uint32_t samples_ptr) { SCOPE_profile_cpu_f("apu"); std::lock_guard lock(lock_); - assert_true(index < maximum_client_count_); + assert_true(index < kMaximumClientCount); assert_true(clients_[index].driver != NULL); (clients_[index].driver)->SubmitFrame(samples_ptr); } @@ -333,18 +337,25 @@ void AudioSystem::UnregisterClient(size_t index) { SCOPE_profile_cpu_f("apu"); std::lock_guard lock(lock_); - assert_true(index < maximum_client_count_); + assert_true(index < kMaximumClientCount); DestroyDriver(clients_[index].driver); clients_[index] = {0}; unused_clients_.push(index); - ResetEvent(client_wait_handles_[index]); + + // drain the semaphore of its count + auto client_semaphore = client_semaphores_[index]; + DWORD wait_result; + do { + wait_result = WaitForSingleObject(client_semaphore, 0); + } while (wait_result == WAIT_OBJECT_0); + assert_true(wait_result == WAIT_TIMEOUT); } void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) { SCOPE_profile_cpu_f("apu"); // Translate this for future use. - uint8_t* out = memory()->TranslatePhysical(data.output_buffer_ptr); + uint8_t* output_buffer = memory()->TranslatePhysical(data.output_buffer_ptr); // What I see: // XMA outputs 2 bytes per sample @@ -371,13 +382,15 @@ void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) { // Output buffers are in raw PCM samples, 256 bytes per block. // Output buffer is a ring buffer. We need to write from the write offset // to the read offset. - uint32_t output_size_bytes = data.output_buffer_block_count * 256; - uint32_t output_write_offset_bytes = data.output_buffer_write_offset * 256; - uint32_t output_read_offset_bytes = data.output_buffer_read_offset * 256; + uint32_t output_capacity = data.output_buffer_block_count * 256; + uint32_t output_read_offset = data.output_buffer_read_offset * 256; + uint32_t output_write_offset = data.output_buffer_write_offset * 256; - RingBuffer output_buffer(out, output_size_bytes, output_read_offset_bytes, output_write_offset_bytes); - size_t output_remaining_bytes = output_buffer.write_size(); + RingBuffer output_rb(output_buffer, output_capacity); + output_rb.set_read_offset(output_read_offset); + output_rb.set_write_offset(output_write_offset); + size_t output_remaining_bytes = output_rb.write_count(); if (!output_remaining_bytes) { // Can't write any more data. Break. // The game will kick us again with a new output buffer later. @@ -395,7 +408,9 @@ void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) { read_bytes = context.decoder->DecodePacket(tmp_buff, 0, output_remaining_bytes); if (read_bytes >= 0) { - output_buffer.Write(tmp_buff, read_bytes); + assert_true((read_bytes % 256) == 0); + auto written_bytes = output_rb.Write(tmp_buff, read_bytes); + assert_true(read_bytes == written_bytes); // Ok. break; @@ -421,11 +436,7 @@ void AudioSystem::ProcessXmaContext(XMAContext& context, XMAContextData& data) { } } - data.output_buffer_write_offset += uint32_t(read_bytes) / 256; - if (data.output_buffer_write_offset > data.output_buffer_block_count) { - // Wraparound! - data.output_buffer_write_offset -= data.output_buffer_block_count; - } + data.output_buffer_write_offset = output_rb.write_offset() / 256; // If we need more data and the input buffers have it, grab it. if (read_bytes) { diff --git a/src/xenia/apu/audio_system.h b/src/xenia/apu/audio_system.h index 7373b4c66..d0c7769d3 100644 --- a/src/xenia/apu/audio_system.h +++ b/src/xenia/apu/audio_system.h @@ -142,13 +142,16 @@ class AudioSystem { void UnregisterClient(size_t index); void SubmitFrame(size_t index, uint32_t samples_ptr); - virtual X_STATUS CreateDriver(size_t index, HANDLE wait_handle, + virtual X_STATUS CreateDriver(size_t index, HANDLE semaphore, AudioDriver** out_driver) = 0; virtual void DestroyDriver(AudioDriver* driver) = 0; virtual uint64_t ReadRegister(uint32_t addr); virtual void WriteRegister(uint32_t addr, uint64_t value); + // TODO(gibbed): respect XAUDIO2_MAX_QUEUED_BUFFERS somehow (ie min(64, XAUDIO2_MAX_QUEUED_BUFFERS)) + static const size_t kMaximumQueuedFrames = 64; + protected: virtual void Initialize(); @@ -204,6 +207,7 @@ class AudioSystem { } registers_; uint32_t register_file_[0xFFFF / 4]; }; + struct XMAContext { uint32_t guest_ptr; xe::mutex lock; @@ -218,16 +222,18 @@ class AudioSystem { std::vector xma_context_free_list_; std::vector xma_context_used_list_; // XMA contexts in use - static const size_t maximum_client_count_ = 8; + static const size_t kMaximumClientCount = 8; struct { AudioDriver* driver; uint32_t callback; uint32_t callback_arg; uint32_t wrapped_callback_arg; - } clients_[maximum_client_count_]; - // Last handle is always there in case we have no clients. - HANDLE client_wait_handles_[maximum_client_count_ + 1]; + } clients_[kMaximumClientCount]; + + HANDLE client_semaphores_[kMaximumClientCount]; + HANDLE shutdown_event_; // Event is always there in case we have no clients. + HANDLE wait_handles_[kMaximumClientCount + 1]; std::queue unused_clients_; }; diff --git a/src/xenia/apu/xaudio2/xaudio2_audio_driver.cc b/src/xenia/apu/xaudio2/xaudio2_audio_driver.cc index 608b201fa..11bee294a 100644 --- a/src/xenia/apu/xaudio2/xaudio2_audio_driver.cc +++ b/src/xenia/apu/xaudio2/xaudio2_audio_driver.cc @@ -20,26 +20,26 @@ namespace xaudio2 { class XAudio2AudioDriver::VoiceCallback : public IXAudio2VoiceCallback { public: - VoiceCallback(HANDLE wait_handle) : wait_handle_(wait_handle) {} + VoiceCallback(HANDLE semaphore) : semaphore_(semaphore) {} ~VoiceCallback() {} void OnStreamEnd() {} void OnVoiceProcessingPassEnd() {} void OnVoiceProcessingPassStart(uint32_t samples_required) {} - void OnBufferEnd(void* context) { SetEvent(wait_handle_); } + void OnBufferEnd(void* context) { assert_true(ReleaseSemaphore(semaphore_, 1, NULL) == TRUE); } void OnBufferStart(void* context) {} void OnLoopEnd(void* context) {} void OnVoiceError(void* context, HRESULT result) {} private: - HANDLE wait_handle_; + HANDLE semaphore_; }; -XAudio2AudioDriver::XAudio2AudioDriver(Emulator* emulator, HANDLE wait) +XAudio2AudioDriver::XAudio2AudioDriver(Emulator* emulator, HANDLE semaphore) : audio_(nullptr), mastering_voice_(nullptr), pcm_voice_(nullptr), - wait_handle_(wait), + semaphore_(semaphore), voice_callback_(nullptr), current_frame_(0), AudioDriver(emulator) {} @@ -61,7 +61,7 @@ const DWORD ChannelMasks[] = { void XAudio2AudioDriver::Initialize() { HRESULT hr; - voice_callback_ = new VoiceCallback(wait_handle_); + voice_callback_ = new VoiceCallback(semaphore_); hr = XAudio2Create(&audio_, 0, XAUDIO2_DEFAULT_PROCESSOR); if (FAILED(hr)) { @@ -123,8 +123,6 @@ void XAudio2AudioDriver::Initialize() { if (FLAGS_mute) { pcm_voice_->SetVolume(0.0f); } - - SetEvent(wait_handle_); } void XAudio2AudioDriver::SubmitFrame(uint32_t frame_ptr) { @@ -169,13 +167,6 @@ void XAudio2AudioDriver::SubmitFrame(uint32_t frame_ptr) { // Update playback ratio to our time scalar. // This will keep audio in sync with the game clock. pcm_voice_->SetFrequencyRatio(float(xe::Clock::guest_time_scalar())); - - XAUDIO2_VOICE_STATE state2; - pcm_voice_->GetState(&state2, XAUDIO2_VOICE_NOSAMPLESPLAYED); - - if (state2.BuffersQueued >= frame_count_) { - ResetEvent(wait_handle_); - } } void XAudio2AudioDriver::Shutdown() { diff --git a/src/xenia/apu/xaudio2/xaudio2_audio_driver.h b/src/xenia/apu/xaudio2/xaudio2_audio_driver.h index cffb5af90..485ad4a4e 100644 --- a/src/xenia/apu/xaudio2/xaudio2_audio_driver.h +++ b/src/xenia/apu/xaudio2/xaudio2_audio_driver.h @@ -21,7 +21,7 @@ namespace xaudio2 { class XAudio2AudioDriver : public AudioDriver { public: - XAudio2AudioDriver(Emulator* emulator, HANDLE wait); + XAudio2AudioDriver(Emulator* emulator, HANDLE semaphore); virtual ~XAudio2AudioDriver(); virtual void Initialize(); @@ -32,7 +32,7 @@ class XAudio2AudioDriver : public AudioDriver { IXAudio2* audio_; IXAudio2MasteringVoice* mastering_voice_; IXAudio2SourceVoice* pcm_voice_; - HANDLE wait_handle_; + HANDLE semaphore_; class VoiceCallback; VoiceCallback* voice_callback_; diff --git a/src/xenia/apu/xaudio2/xaudio2_audio_system.cc b/src/xenia/apu/xaudio2/xaudio2_audio_system.cc index ee549e45b..1d5f85e68 100644 --- a/src/xenia/apu/xaudio2/xaudio2_audio_system.cc +++ b/src/xenia/apu/xaudio2/xaudio2_audio_system.cc @@ -25,10 +25,10 @@ XAudio2AudioSystem::~XAudio2AudioSystem() {} void XAudio2AudioSystem::Initialize() { AudioSystem::Initialize(); } -X_STATUS XAudio2AudioSystem::CreateDriver(size_t index, HANDLE wait, +X_STATUS XAudio2AudioSystem::CreateDriver(size_t index, HANDLE semaphore, AudioDriver** out_driver) { assert_not_null(out_driver); - auto driver = new XAudio2AudioDriver(emulator_, wait); + auto driver = new XAudio2AudioDriver(emulator_, semaphore); driver->Initialize(); *out_driver = driver; return X_STATUS_SUCCESS; diff --git a/src/xenia/apu/xaudio2/xaudio2_audio_system.h b/src/xenia/apu/xaudio2/xaudio2_audio_system.h index b0e75f1da..dd6fd1e47 100644 --- a/src/xenia/apu/xaudio2/xaudio2_audio_system.h +++ b/src/xenia/apu/xaudio2/xaudio2_audio_system.h @@ -24,7 +24,7 @@ class XAudio2AudioSystem : public AudioSystem { XAudio2AudioSystem(Emulator* emulator); virtual ~XAudio2AudioSystem(); - virtual X_RESULT CreateDriver(size_t index, HANDLE wait, + virtual X_RESULT CreateDriver(size_t index, HANDLE semaphore, AudioDriver** out_driver); virtual void DestroyDriver(AudioDriver* driver); diff --git a/src/xenia/base/ring_buffer.cc b/src/xenia/base/ring_buffer.cc index 45225325b..4f4b37a8b 100644 --- a/src/xenia/base/ring_buffer.cc +++ b/src/xenia/base/ring_buffer.cc @@ -14,60 +14,50 @@ namespace xe { -RingBuffer::RingBuffer(uint8_t* raw_buffer, size_t size, size_t read_offset, size_t write_offset) - : raw_buffer_(raw_buffer) - , size_(size) - , read_offset_(read_offset) - , write_offset_(write_offset) {} +RingBuffer::RingBuffer(uint8_t* buffer, size_t capacity) + : buffer_(buffer) + , capacity_(capacity) + , read_offset_(0) + , write_offset_(0) {} -size_t RingBuffer::Skip(size_t num_bytes) { - num_bytes = std::min(read_size(), num_bytes); - if (read_offset_ + num_bytes < size_) { - read_offset_ += num_bytes; - } else { - read_offset_ = num_bytes - (size_ - read_offset_); - } - return num_bytes; -} - -size_t RingBuffer::Read(uint8_t* buffer, size_t num_bytes) { - num_bytes = std::min(read_size(), num_bytes); - if (!num_bytes) { +size_t RingBuffer::Read(uint8_t* buffer, size_t count) { + count = std::min(count, capacity_); + if (!count) { return 0; } - if (read_offset_ + num_bytes < size_) { - std::memcpy(buffer, raw_buffer_ + read_offset_, num_bytes); - read_offset_ += num_bytes; + if (read_offset_ + count < capacity_) { + std::memcpy(buffer, buffer_ + read_offset_, count); + read_offset_ += count; } else { - size_t left_half = size_ - read_offset_; - size_t right_half = size_ - left_half; - std::memcpy(buffer, raw_buffer_ + read_offset_, left_half); - std::memcpy(buffer + left_half, raw_buffer_, right_half); + size_t left_half = capacity_ - read_offset_; + size_t right_half = count - left_half; + std::memcpy(buffer, buffer_ + read_offset_, left_half); + std::memcpy(buffer + left_half, buffer_, right_half); read_offset_ = right_half; } - return num_bytes; + return count; } -size_t RingBuffer::Write(uint8_t* buffer, size_t num_bytes) { - num_bytes = std::min(num_bytes, write_size()); - if (!num_bytes) { +size_t RingBuffer::Write(uint8_t* buffer, size_t count) { + count = std::min(count, capacity_); + if (!count) { return 0; } - if (write_offset_ + num_bytes < size_) { - std::memcpy(raw_buffer_ + write_offset_, buffer, num_bytes); - write_offset_ += num_bytes; + if (write_offset_ + count < capacity_) { + std::memcpy(buffer_ + write_offset_, buffer, count); + write_offset_ += count; } else { - size_t left_half = size_ - write_offset_; - size_t right_half = size_ - left_half; - std::memcpy(raw_buffer_ + write_offset_, buffer, left_half); - std::memcpy(raw_buffer_, buffer + left_half, right_half); + size_t left_half = capacity_ - write_offset_; + size_t right_half = count - left_half; + std::memcpy(buffer_ + write_offset_, buffer, left_half); + std::memcpy(buffer_, buffer + left_half, right_half); write_offset_ = right_half; } - return num_bytes; + return count; } } // namespace xe diff --git a/src/xenia/base/ring_buffer.h b/src/xenia/base/ring_buffer.h index 4d3830195..0a2c8825d 100644 --- a/src/xenia/base/ring_buffer.h +++ b/src/xenia/base/ring_buffer.h @@ -18,38 +18,47 @@ namespace xe { class RingBuffer { public: - RingBuffer(uint8_t* raw_buffer, size_t size, size_t read_offset, size_t write_offset); + RingBuffer(uint8_t* buffer, size_t capacity); - size_t Read(uint8_t* buffer, size_t num_bytes); - size_t Skip(size_t num_bytes); - size_t Write(uint8_t* buffer, size_t num_bytes); + size_t Read(uint8_t* buffer, size_t count); + size_t Write(uint8_t* buffer, size_t count); + + uint8_t* buffer() { return buffer_; } + size_t capacity() { return capacity_; } size_t read_offset() { return read_offset_; } - size_t write_offset() { return write_offset_; } - - size_t read_size() { + size_t read_count() { if (read_offset_ == write_offset_) { return 0; + } else if (read_offset_ < write_offset_) { + return write_offset_ - read_offset_; + } else { + return (capacity_ - read_offset_) + write_offset_; } - if (read_offset_ < write_offset_) { - return write_offset_ - read_offset_; - } - return (size_ - read_offset_) + write_offset_; } - size_t write_size() { - if (write_offset_ == read_offset_) { - return size_; - } - if (write_offset_ < read_offset_) { + size_t write_offset() { return write_offset_; } + size_t write_count() { + if (read_offset_ == write_offset_) { + return capacity_; + } else if (write_offset_ < read_offset_) { return read_offset_ - write_offset_; + } else { + return (capacity_ - write_offset_) + read_offset_; } - return (size_ - write_offset_) + read_offset_; + } + + void set_read_offset(size_t offset) { + read_offset_ = offset % capacity_; + } + + void set_write_offset(size_t offset) { + write_offset_ = offset % capacity_; } private: - uint8_t* raw_buffer_; - size_t size_; + uint8_t* buffer_; + size_t capacity_; size_t read_offset_; size_t write_offset_; };