From 00a1c392e7252eaad82b50119037e01bf6b92a73 Mon Sep 17 00:00:00 2001 From: xbzk Date: Thu, 10 Sep 2026 22:29:35 +0200 Subject: [PATCH] [opus] proper limit and error code for opus decoder instances (#4384) - [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions). - [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md) - [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability. ------------------- This is intended to fix Hades II save loading issue. By analyzing panic dump it was found out that the error code was 0x160A. By decoding firmware it was found that error comes from 'nn::codec::InitializeHardwareOpusDecoder'. Further decoding revealed that SDK tries to allocate multiple opus instances, up to the fw limit of 24 instances, and when it receives return error ResultOutOfOpusDecoders (385), a software fallback is gracefully used. Implementing the limitation and the limit reached error code return makes saves load properly, multiple times in a row in both windows and android. I still wonder why the game allocates too many parallel instances, but the current implementation is a real fw compliant solution, and whether the 24+ is real or some leak may be investigated later, or not. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4384 Reviewed-by: lizzie Reviewed-by: MaranBr --- src/audio_core/opus/decoder.cpp | 16 ++++++++++++++++ src/audio_core/opus/hardware_opus.cpp | 20 ++++++++++++++++++++ src/audio_core/opus/hardware_opus.h | 8 ++++++++ src/core/hle/service/audio/errors.h | 3 ++- 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/audio_core/opus/decoder.cpp b/src/audio_core/opus/decoder.cpp index e3a8fe1bfa..1c13cb60e9 100644 --- a/src/audio_core/opus/decoder.cpp +++ b/src/audio_core/opus/decoder.cpp @@ -8,6 +8,7 @@ #include "audio_core/opus/hardware_opus.h" #include "audio_core/opus/parameters.h" #include "common/alignment.h" +#include "common/scope_exit.h" #include "common/swap.h" #include "core/core.h" @@ -28,10 +29,18 @@ OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_) OpusDecoder::~OpusDecoder() { if (decode_object_initialized) { hardware_opus.ShutdownDecodeObject(shared_buffer.data(), shared_buffer.size()); + hardware_opus.UnregisterDecoder(this); } } Result OpusDecoder::Initialize(const OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) { + R_TRY(hardware_opus.RegisterDecoder(this)); + SCOPE_EXIT { + if (!decode_object_initialized) { + hardware_opus.UnregisterDecoder(this); + } + }; + auto frame_size{params.use_large_frame_size ? 5760 : 1920}; shared_buffer.resize(transfer_memory_size); shared_memory_mapped = true; @@ -61,6 +70,13 @@ Result OpusDecoder::Initialize(const OpusParametersEx& params, Kernel::KTransfer } Result OpusDecoder::Initialize(const OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) { + R_TRY(hardware_opus.RegisterDecoder(this)); + SCOPE_EXIT { + if (!decode_object_initialized) { + hardware_opus.UnregisterDecoder(this); + } + }; + auto frame_size{params.use_large_frame_size ? 5760 : 1920}; shared_buffer.resize(transfer_memory_size, 0); shared_memory_mapped = true; diff --git a/src/audio_core/opus/hardware_opus.cpp b/src/audio_core/opus/hardware_opus.cpp index 0037a3e503..04591c10b6 100644 --- a/src/audio_core/opus/hardware_opus.cpp +++ b/src/audio_core/opus/hardware_opus.cpp @@ -4,6 +4,7 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include "audio_core/audio_core.h" @@ -45,6 +46,25 @@ HardwareOpus::HardwareOpus(Core::System& system_) opus_decoder.SetSharedMemory(shared_memory); } +Result HardwareOpus::RegisterDecoder(OpusDecoder* decoder) { + std::scoped_lock l{mutex}; + const auto slot = std::ranges::find(decoders, nullptr); + if (slot == decoders.end()) { + R_THROW(ResultOutOfOpusDecoders); + } + *slot = decoder; + R_SUCCEED(); +} + +void HardwareOpus::UnregisterDecoder(OpusDecoder* decoder) { + std::scoped_lock l{mutex}; + const auto slot = std::ranges::find(decoders, decoder); + if (slot == decoders.end()) { + return; + } + *slot = nullptr; +} + u32 HardwareOpus::GetWorkBufferSize(u32 channel) { if (!opus_decoder.IsRunning()) { return 0; diff --git a/src/audio_core/opus/hardware_opus.h b/src/audio_core/opus/hardware_opus.h index caa7468401..b6c7fbe6c8 100644 --- a/src/audio_core/opus/hardware_opus.h +++ b/src/audio_core/opus/hardware_opus.h @@ -1,8 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include #include #include @@ -12,9 +16,12 @@ #include "core/hle/service/audio/errors.h" namespace AudioCore::OpusDecoder { +class OpusDecoder; class HardwareOpus { public: HardwareOpus(Core::System& system); + Result RegisterDecoder(OpusDecoder* decoder); + void UnregisterDecoder(OpusDecoder* decoder); u32 GetWorkBufferSize(u32 channel); u32 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count); @@ -39,6 +46,7 @@ public: private: Core::System& system; std::mutex mutex; + std::array decoders{}; ADSP::OpusDecoder::OpusDecoder& opus_decoder; ADSP::OpusDecoder::SharedMemory shared_memory; }; diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h index 8062820de3..553b4ee360 100644 --- a/src/core/hle/service/audio/errors.h +++ b/src/core/hle/service/audio/errors.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project @@ -33,6 +33,7 @@ constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4}; constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3}; constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2}; constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259}; +constexpr Result ResultOutOfOpusDecoders{ErrorModule::HwOpus, 385}; constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001}; constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002};