From b94ab4acbc357ea3c31d87e80a51e70fd0a929c8 Mon Sep 17 00:00:00 2001 From: "Dr. Chat" Date: Mon, 7 Dec 2015 23:34:34 -0600 Subject: [PATCH] AudioSystem Save/Restore --- src/xenia/apu/audio_system.cc | 117 ++++++++++++++++++++++++++++++++-- src/xenia/apu/audio_system.h | 11 ++++ src/xenia/emulator.cc | 10 +++ 3 files changed, 134 insertions(+), 4 deletions(-) diff --git a/src/xenia/apu/audio_system.cc b/src/xenia/apu/audio_system.cc index 51c3c5a33..858a24f8a 100644 --- a/src/xenia/apu/audio_system.cc +++ b/src/xenia/apu/audio_system.cc @@ -12,6 +12,7 @@ #include "xenia/apu/apu_flags.h" #include "xenia/apu/audio_driver.h" #include "xenia/apu/xma_decoder.h" +#include "xenia/base/byte_stream.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/base/profiling.h" @@ -46,10 +47,12 @@ AudioSystem::AudioSystem(cpu::Processor* processor) xe::threading::Semaphore::Create(0, kMaximumQueuedFrames); wait_handles_[i] = client_semaphores_[i].get(); } - shutdown_event_ = xe::threading::Event::CreateManualResetEvent(false); + shutdown_event_ = xe::threading::Event::CreateAutoResetEvent(false); wait_handles_[kMaximumClientCount] = shutdown_event_.get(); xma_decoder_ = std::make_unique(processor_); + + resume_event_ = xe::threading::Event::CreateAutoResetEvent(false); } AudioSystem::~AudioSystem() { @@ -84,16 +87,31 @@ void AudioSystem::WorkerThreadMain() { // Main run loop. 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 + // (signaling a sample has finished playing) auto result = xe::threading::WaitAny(wait_handles_, xe::countof(wait_handles_), true); - if (result.first == xe::threading::WaitResult::kFailed || - (result.first == xe::threading::WaitResult::kSuccess && - result.second == kMaximumClientCount)) { + if (result.first == xe::threading::WaitResult::kFailed) { + // TODO: Assert? continue; } + if (result.first == threading::WaitResult::kSuccess && + result.second == kMaximumClientCount) { + // Shutdown event signaled. + if (paused_) { + pause_fence_.Signal(); + threading::Wait(resume_event_.get(), false); + } + + continue; + } + + // Number of clients pumped size_t pumped = 0; if (result.first == xe::threading::WaitResult::kSuccess) { + // Start from the first handle signaled and continue down. size_t index = result.second; do { auto global_lock = global_critical_region_.Acquire(); @@ -206,5 +224,96 @@ void AudioSystem::UnregisterClient(size_t index) { assert_true(wait_result == xe::threading::WaitResult::kTimeout); } +bool AudioSystem::Save(ByteStream* stream) { + stream->Write('XAUD'); + + // Count the number of used clients first. + // Any gaps should be handled gracefully. + uint32_t used_clients = 0; + for (int i = 0; i < kMaximumClientCount; i++) { + if (clients_[i].in_use) { + used_clients++; + } + } + + stream->Write(used_clients); + for (uint32_t i = 0; i < kMaximumClientCount; i++) { + auto& client = clients_[i]; + if (!client.in_use) { + continue; + } + + stream->Write(i); + stream->Write(client.callback); + stream->Write(client.callback_arg); + stream->Write(client.wrapped_callback_arg); + } + + return true; +} + +bool AudioSystem::Restore(ByteStream* stream) { + if (stream->Read() != 'XAUD') { + XELOGE("AudioSystem::Restore - Invalid magic value!"); + return false; + } + + uint32_t num_clients = stream->Read(); + for (uint32_t i = 0; i < num_clients; i++) { + auto id = stream->Read(); + assert_true(id < kMaximumClientCount); + + auto& client = clients_[id]; + client.callback = stream->Read(); + client.callback_arg = stream->Read(); + client.wrapped_callback_arg = stream->Read(); + + client.in_use = true; + + // Reset the semaphore and recreate the driver ourselves. + if (client.driver) { + UnregisterClient(id); + } + + auto client_semaphore = client_semaphores_[id].get(); + auto ret = client_semaphore->Release(kMaximumQueuedFrames, nullptr); + assert_true(ret); + + AudioDriver* driver = nullptr; + auto status = CreateDriver(id, client_semaphore, &driver); + if (XFAILED(status)) { + XELOGE( + "AudioSystem::Restore - Call to CreateDriver failed with status %.8X", + status); + return false; + } + + assert_not_null(driver); + client.driver = driver; + } + + return true; +} + +void AudioSystem::Pause() { + if (paused_) { + return; + } + paused_ = true; + + // Kind of a hack, but it works. + shutdown_event_->Set(); + pause_fence_.Wait(); +} + +void AudioSystem::Resume() { + if (!paused_) { + return; + } + paused_ = false; + + resume_event_->Set(); +} + } // namespace apu } // namespace xe diff --git a/src/xenia/apu/audio_system.h b/src/xenia/apu/audio_system.h index 964420732..014a1dc8d 100644 --- a/src/xenia/apu/audio_system.h +++ b/src/xenia/apu/audio_system.h @@ -42,6 +42,13 @@ class AudioSystem { void UnregisterClient(size_t index); void SubmitFrame(size_t index, uint32_t samples_ptr); + bool Save(ByteStream* stream); + bool Restore(ByteStream* stream); + + bool is_paused() const { return paused_; } + void Pause(); + void Resume(); + protected: explicit AudioSystem(cpu::Processor* processor); @@ -82,6 +89,10 @@ class AudioSystem { // Event is always there in case we have no clients. std::unique_ptr shutdown_event_; xe::threading::WaitHandle* wait_handles_[kMaximumClientCount + 1]; + + bool paused_ = false; + threading::Fence pause_fence_; + std::unique_ptr resume_event_; }; } // namespace apu diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 620bb913a..d854fb525 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -299,6 +299,7 @@ void Emulator::Pause() { // Don't hold the lock on this (so any waits follow through) graphics_system_->Pause(); + audio_system_->Pause(); auto lock = global_critical_region::AcquireDirect(); auto threads = @@ -324,6 +325,7 @@ void Emulator::Resume() { XELOGD("! EMULATOR RESUMED !"); graphics_system_->Resume(); + audio_system_->Resume(); auto threads = kernel_state()->object_table()->GetObjectsByType( @@ -355,6 +357,7 @@ bool Emulator::SaveToFile(const std::wstring& path) { // It's important we don't hold the global lock here! XThreads need to step // forward (possibly through guarded regions) without worry! graphics_system_->Save(&stream); + audio_system_->Save(&stream); kernel_state_->Save(&stream); memory_->Save(&stream); map->Close(stream.offset()); @@ -383,12 +386,19 @@ bool Emulator::RestoreFromFile(const std::wstring& path) { } if (!graphics_system_->Restore(&stream)) { + XELOGE("Could not restore graphics system!"); + return false; + } + if (!audio_system_->Restore(&stream)) { + XELOGE("Could not restore audio system!"); return false; } if (!kernel_state_->Restore(&stream)) { + XELOGE("Could not restore kernel state!"); return false; } if (!memory_->Restore(&stream)) { + XELOGE("Could not restore memory!"); return false; }