From fe92815b0491009b36100f7fd9531d50bb1513a6 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Tue, 23 Dec 2025 22:57:56 +0100 Subject: [PATCH] audio_core: Add DSP sleep and wakeup functionality (#1529) --- src/audio_core/hle/aac_decoder.cpp | 6 ++- src/audio_core/hle/aac_decoder.h | 2 + src/audio_core/hle/decoder.h | 4 +- src/audio_core/hle/hle.cpp | 75 +++++++++++++++++++++++++----- src/audio_core/hle/mixers.cpp | 14 +++++- src/audio_core/hle/mixers.h | 27 ++++++++--- src/audio_core/hle/shared_memory.h | 3 +- src/audio_core/hle/source.cpp | 14 +++++- src/audio_core/hle/source.h | 15 ++++-- 9 files changed, 133 insertions(+), 27 deletions(-) diff --git a/src/audio_core/hle/aac_decoder.cpp b/src/audio_core/hle/aac_decoder.cpp index aba724978..054674367 100644 --- a/src/audio_core/hle/aac_decoder.cpp +++ b/src/audio_core/hle/aac_decoder.cpp @@ -8,7 +8,7 @@ namespace AudioCore::HLE { AACDecoder::AACDecoder(Memory::MemorySystem& memory) : memory(memory) { - OpenNewDecoder(); + Reset(); } AACDecoder::~AACDecoder() { @@ -63,6 +63,10 @@ BinaryMessage AACDecoder::ProcessRequest(const BinaryMessage& request) { } } +void AACDecoder::Reset() { + OpenNewDecoder(); +} + BinaryMessage AACDecoder::Decode(const BinaryMessage& request) { BinaryMessage response{}; response.header.codec = request.header.codec; diff --git a/src/audio_core/hle/aac_decoder.h b/src/audio_core/hle/aac_decoder.h index eb9d62295..1edbabd0b 100644 --- a/src/audio_core/hle/aac_decoder.h +++ b/src/audio_core/hle/aac_decoder.h @@ -16,6 +16,8 @@ public: ~AACDecoder() override; BinaryMessage ProcessRequest(const BinaryMessage& request) override; + void Reset() override; + private: BinaryMessage Decode(const BinaryMessage& request); bool OpenNewDecoder(); diff --git a/src/audio_core/hle/decoder.h b/src/audio_core/hle/decoder.h index 53365f45c..87bc3ef2c 100644 --- a/src/audio_core/hle/decoder.h +++ b/src/audio_core/hle/decoder.h @@ -1,4 +1,4 @@ -// Copyright 2018 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -137,6 +137,8 @@ class DecoderBase { public: virtual ~DecoderBase() = default; virtual BinaryMessage ProcessRequest(const BinaryMessage& request) = 0; + + virtual void Reset() = 0; }; } // namespace AudioCore::HLE diff --git a/src/audio_core/hle/hle.cpp b/src/audio_core/hle/hle.cpp index fa3825ef6..5c89c022f 100644 --- a/src/audio_core/hle/hle.cpp +++ b/src/audio_core/hle/hle.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -64,6 +65,10 @@ public: std::function handler); private: + void Initialize(); + void Sleep(); + void Wakeup(); + void ResetPipes(); void WriteU16(DspPipe pipe_number, u16 value); void AudioPipeWriteStructAddresses(); @@ -92,6 +97,8 @@ private: }}; HLE::Mixers mixers{}; + HLE::DspMemory backup_dsp_memory; + DspHle& parent; Core::Timing& core_timing; Core::TimingEventType* tick_event{}; @@ -102,6 +109,8 @@ private: template void serialize(Archive& ar, const unsigned int) { + ar& boost::serialization::make_binary_object(backup_dsp_memory.raw_memory.data(), + backup_dsp_memory.raw_memory.size()); ar & dsp_state; ar & pipe_data; ar & sources; @@ -217,8 +226,6 @@ void DspHle::Impl::PipeWrite(DspPipe pipe_number, std::span buffer) { Sleep = 3, }; - // The difference between Initialize and Wakeup is that Input state is maintained - // when sleeping but isn't when turning it off and on again. (TODO: Implement this.) // Waking up from sleep garbles some of the structs in the memory region. (TODO: // Implement this.) Applications store away the state of these structs before // sleeping and reset it back after wakeup on behalf of the DSP. @@ -226,7 +233,7 @@ void DspHle::Impl::PipeWrite(DspPipe pipe_number, std::span buffer) { switch (static_cast(buffer[0])) { case StateChange::Initialize: LOG_INFO(Audio_DSP, "Application has requested initialization of DSP hardware"); - ResetPipes(); + Initialize(); AudioPipeWriteStructAddresses(); dsp_state = DspState::On; break; @@ -236,13 +243,13 @@ void DspHle::Impl::PipeWrite(DspPipe pipe_number, std::span buffer) { break; case StateChange::Wakeup: LOG_INFO(Audio_DSP, "Application has requested wakeup of DSP hardware"); - ResetPipes(); + Wakeup(); AudioPipeWriteStructAddresses(); dsp_state = DspState::On; break; case StateChange::Sleep: LOG_INFO(Audio_DSP, "Application has requested sleep of DSP hardware"); - UNIMPLEMENTED(); + Sleep(); AudioPipeWriteStructAddresses(); dsp_state = DspState::Sleeping; break; @@ -290,11 +297,51 @@ void DspHle::Impl::SetInterruptHandler( interrupt_handler = handler; } +void DspHle::Impl::Initialize() { + // TODO(PabloMK7): This is NOT the right way to do this, + // but it is close enough. This makes sure the DSP state + // is clean and consistent every time the HW is initialized, + // but what is exactly reset needs to be figured out. + dsp_memory->raw_memory.fill(0); + mixers.Reset(); + for (auto& s : sources) { + s.Reset(); + } + aac_decoder->Reset(); + ResetPipes(); +} + +void DspHle::Impl::Sleep() { + // TODO(PabloMK7): This is NOT the right way to do this, + // but it is close enough. What state is saved on + // real hardware still not figured out. + backup_dsp_memory.raw_memory = dsp_memory->raw_memory; + mixers.Sleep(); + for (auto& s : sources) { + s.Sleep(); + } + // TODO(PabloMK7): Figure out if we need to save the state + // of the AAC decoder, probably not. +} + +void DspHle::Impl::Wakeup() { + // TODO(PabloMK7): This is NOT the right way to do this, + // but it is close enough. What state is restored on + // real hardware still not figured out. + dsp_memory->raw_memory = backup_dsp_memory.raw_memory; + backup_dsp_memory.raw_memory.fill(0); + mixers.Wakeup(); + for (auto& s : sources) { + s.Wakeup(); + } + aac_decoder->Reset(); + ResetPipes(); +} + void DspHle::Impl::ResetPipes() { for (auto& data : pipe_data) { data.clear(); } - dsp_state = DspState::Off; } void DspHle::Impl::WriteU16(DspPipe pipe_number, u16 value) { @@ -396,15 +443,19 @@ StereoFrame16 DspHle::Impl::GenerateCurrentFrame() { } bool DspHle::Impl::Tick() { - StereoFrame16 current_frame = {}; + bool is_on = GetDspState() == DspState::On; - // TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing to - // shared memory region) - current_frame = GenerateCurrentFrame(); + if (is_on) { + StereoFrame16 current_frame = {}; - parent.OutputFrame(std::move(current_frame)); + // TODO: Check dsp::DSP semaphore (which indicates emulated application has finished writing + // to shared memory region) + current_frame = GenerateCurrentFrame(); - return GetDspState() == DspState::On; + parent.OutputFrame(std::move(current_frame)); + } + + return is_on; } void DspHle::Impl::AudioTickCallback(s64 cycles_late) { diff --git a/src/audio_core/hle/mixers.cpp b/src/audio_core/hle/mixers.cpp index e3a838886..cfe99244a 100644 --- a/src/audio_core/hle/mixers.cpp +++ b/src/audio_core/hle/mixers.cpp @@ -1,4 +1,4 @@ -// Copyright 2016 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -15,6 +15,18 @@ void Mixers::Reset() { state = {}; } +void Mixers::Sleep() { + backup_state = state; + backup_frame = current_frame; +} + +void Mixers::Wakeup() { + state = backup_state; + current_frame = backup_frame; + backup_state = {}; + backup_frame.fill({}); +} + DspStatus Mixers::Tick(DspConfiguration& config, const IntermediateMixSamples& read_samples, IntermediateMixSamples& write_samples, const std::array& input) { diff --git a/src/audio_core/hle/mixers.h b/src/audio_core/hle/mixers.h index b09654dfc..95d9e86e3 100644 --- a/src/audio_core/hle/mixers.h +++ b/src/audio_core/hle/mixers.h @@ -1,4 +1,4 @@ -// Copyright 2016 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -19,6 +19,9 @@ public: void Reset(); + void Sleep(); + void Wakeup(); + DspStatus Tick(DspConfiguration& config, const IntermediateMixSamples& read_samples, IntermediateMixSamples& write_samples, const std::array& input); @@ -28,10 +31,11 @@ public: private: StereoFrame16 current_frame = {}; + StereoFrame16 backup_frame = {}; // TODO(PabloMK7): Check if we actually need this using OutputFormat = DspConfiguration::OutputFormat; - struct { + struct MixerState { std::array intermediate_mixer_volume = {}; std::array aux_bus_enable = {}; @@ -39,7 +43,17 @@ private: OutputFormat output_format = OutputFormat::Stereo; - } state; + template + void serialize(Archive& ar, const unsigned int) { + ar & intermediate_mixer_volume; + ar & aux_bus_enable; + ar & intermediate_mix_buffer; + ar & output_format; + } + }; + + MixerState state; + MixerState backup_state; /// INTERNAL: Update our internal state based on the current config. void ParseConfig(DspConfiguration& config); @@ -58,10 +72,9 @@ private: template void serialize(Archive& ar, const unsigned int) { ar & current_frame; - ar & state.intermediate_mixer_volume; - ar & state.aux_bus_enable; - ar & state.intermediate_mix_buffer; - ar & state.output_format; + ar & backup_frame; + ar & state; + ar & backup_state; } friend class boost::serialization::access; }; diff --git a/src/audio_core/hle/shared_memory.h b/src/audio_core/hle/shared_memory.h index 41dc4e25c..194f517a9 100644 --- a/src/audio_core/hle/shared_memory.h +++ b/src/audio_core/hle/shared_memory.h @@ -1,4 +1,4 @@ -// Copyright 2016 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -533,6 +533,7 @@ union DspMemory { u8 unused_2[0x8000]; }; }; +static_assert(sizeof(DspMemory) == 0x80000, "Incorrect DSP memory size"); static_assert(offsetof(DspMemory, region_0) == region0_offset, "DSP region 0 is at the wrong offset"); static_assert(offsetof(DspMemory, region_1) == region1_offset, diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp index c90201bbd..018368704 100644 --- a/src/audio_core/hle/source.cpp +++ b/src/audio_core/hle/source.cpp @@ -1,4 +1,4 @@ -// Copyright 2016 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -44,6 +44,18 @@ void Source::Reset() { state = {}; } +void Source::Sleep() { + backup_frame = current_frame; + backup_state = state; +} + +void Source::Wakeup() { + current_frame = backup_frame; + state = backup_state; + backup_frame.fill({}); + backup_state = {}; +} + void Source::SetMemory(Memory::MemorySystem& memory) { memory_system = &memory; } diff --git a/src/audio_core/hle/source.h b/src/audio_core/hle/source.h index 2073e9033..c0c1fdc1a 100644 --- a/src/audio_core/hle/source.h +++ b/src/audio_core/hle/source.h @@ -1,4 +1,4 @@ -// Copyright 2016 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -42,6 +42,9 @@ public: /// Resets internal state. void Reset(); + void Sleep(); + void Wakeup(); + /// Sets the memory system to read data from void SetMemory(Memory::MemorySystem& memory); @@ -68,6 +71,7 @@ private: const std::size_t source_id; Memory::MemorySystem* memory_system{}; StereoFrame16 current_frame; + StereoFrame16 backup_frame; // TODO(PabloMK7): Check if we actually need this using Format = SourceConfiguration::Configuration::Format; using InterpolationMode = SourceConfiguration::Configuration::InterpolationMode; @@ -116,7 +120,7 @@ private: } }; - struct { + struct SourceState { // State variables @@ -179,8 +183,10 @@ private: ar & interpolation_mode; } friend class boost::serialization::access; + }; - } state; + SourceState state; + SourceState backup_state; // Internal functions @@ -197,6 +203,9 @@ private: template void serialize(Archive& ar, const unsigned int) { ar & state; + ar & backup_state; + ar & current_frame; + ar & backup_frame; } friend class boost::serialization::access; };