diff --git a/.patch/opus/0001-disable-clang-runtime-neon.patch b/.patch/opus/0001-disable-clang-runtime-neon.patch deleted file mode 100644 index 27a905c366..0000000000 --- a/.patch/opus/0001-disable-clang-runtime-neon.patch +++ /dev/null @@ -1,28 +0,0 @@ -From cc15da16e533b2a801934eab2dfeaf3c3949a1dc Mon Sep 17 00:00:00 2001 -From: crueter -Date: Mon, 8 Sep 2025 12:28:55 -0400 -Subject: [PATCH] [cmake] disable NEON runtime check on clang-cl - -When enabling runtime NEON checking for clang-cl, the linker would error out with `undefined symbol: __emit`, since clang doesn't actually implement this instruction. Therefore it makes sense to disable the runtime check by default on this platform, until either this is fixed or a clang-cl compatible intrinsic check is added (I don't have enough knowledge of MSVC to do this) ---- - cmake/OpusConfig.cmake | 7 ++++++- - 1 file changed, 6 insertions(+), 1 deletion(-) - -diff --git a/cmake/OpusConfig.cmake b/cmake/OpusConfig.cmake -index e9319fbad..d0f459e88 100644 ---- a/cmake/OpusConfig.cmake -+++ b/cmake/OpusConfig.cmake -@@ -71,7 +71,12 @@ elseif(OPUS_CPU_ARM AND NOT OPUS_DISABLE_INTRINSICS) - opus_detect_neon(COMPILER_SUPPORT_NEON) - if(COMPILER_SUPPORT_NEON) - option(OPUS_USE_NEON "Option to enable NEON" ON) -- option(OPUS_MAY_HAVE_NEON "Does runtime check for neon support" ON) -+ if (MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang") -+ set(NEON_RUNTIME_CHECK_DEFAULT OFF) -+ else() -+ set(NEON_RUNTIME_CHECK_DEFAULT ON) -+ endif() -+ option(OPUS_MAY_HAVE_NEON "Does runtime check for neon support" ${NEON_RUNTIME_CHECK_DEFAULT}) - option(OPUS_PRESUME_NEON "Assume target CPU has NEON support" OFF) - if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") - set(OPUS_PRESUME_NEON ON) diff --git a/.patch/opus/0002-no-install.patch b/.patch/opus/0002-no-install.patch deleted file mode 100644 index 833a3e4178..0000000000 --- a/.patch/opus/0002-no-install.patch +++ /dev/null @@ -1,153 +0,0 @@ -From bf455b67b4eaa446ffae5d25410b141b7b1b1082 Mon Sep 17 00:00:00 2001 -From: crueter -Date: Mon, 8 Sep 2025 12:08:20 -0400 -Subject: [PATCH] [cmake] `OPUS_INSTALL` option; only default install if root - project - -Signed-off-by: crueter ---- - CMakeLists.txt | 112 ++++++++++++++++++++++++++++--------------------- - 1 file changed, 64 insertions(+), 48 deletions(-) - -diff --git a/CMakeLists.txt b/CMakeLists.txt -index fcf034b19..08b5e16f8 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -4,6 +4,13 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") - include(OpusPackageVersion) - get_package_version(PACKAGE_VERSION PROJECT_VERSION) - -+# root project detection -+if(DEFINED PROJECT_NAME) -+ set(root_project OFF) -+else() -+ set(root_project ON) -+endif() -+ - project(Opus LANGUAGES C VERSION ${PROJECT_VERSION}) - - include(OpusFunctions) -@@ -83,12 +90,16 @@ set(OPUS_DNN_FLOAT_DEBUG_HELP_STR "Run DNN computations as float for debugging p - option(OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR} OFF) - add_feature_info(OPUS_DNN_FLOAT_DEBUG OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR}) - -+set(OPUS_INSTALL_HELP_STR "Install Opus targets") -+option(OPUS_INSTALL ${OPUS_INSTALL_HELP_STR} ${root_project}) -+add_feature_info(OPUS_INSTALL OPUS_INSTALL ${OPUS_INSTALL_HELP_STR}) -+ - set(OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR "install pkg-config module.") --option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ON) -+option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ${OPUS_INSTALL}) - add_feature_info(OPUS_INSTALL_PKG_CONFIG_MODULE OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR}) - - set(OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR "install CMake package config module.") --option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ON) -+option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ${OPUS_INSTALL}) - add_feature_info(OPUS_INSTALL_CMAKE_CONFIG_MODULE OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR}) - - set(OPUS_DRED_HELP_STR "enable DRED.") -@@ -613,53 +624,58 @@ if(OPUS_BUILD_FRAMEWORK) - OUTPUT_NAME Opus) - endif() - --install(TARGETS opus -- EXPORT OpusTargets -- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} -- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -- FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX} -- PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/opus) -- --if(OPUS_INSTALL_PKG_CONFIG_MODULE) -- set(prefix ${CMAKE_INSTALL_PREFIX}) -- set(exec_prefix ${CMAKE_INSTALL_PREFIX}) -- set(libdir ${CMAKE_INSTALL_FULL_LIBDIR}) -- set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR}) -- set(VERSION ${PACKAGE_VERSION}) -- if(HAVE_LIBM) -- set(LIBM "-lm") -+if (OPUS_INSTALL) -+ install(TARGETS opus -+ EXPORT OpusTargets -+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} -+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -+ FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX} -+ PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/opus) -+ -+ if(OPUS_INSTALL_PKG_CONFIG_MODULE) -+ set(prefix ${CMAKE_INSTALL_PREFIX}) -+ set(exec_prefix ${CMAKE_INSTALL_PREFIX}) -+ set(libdir ${CMAKE_INSTALL_FULL_LIBDIR}) -+ set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR}) -+ set(VERSION ${PACKAGE_VERSION}) -+ if(HAVE_LIBM) -+ set(LIBM "-lm") -+ endif() -+ configure_file(opus.pc.in opus.pc) -+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/opus.pc -+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) -+ endif() -+ -+ if(OPUS_INSTALL_CMAKE_CONFIG_MODULE) -+ set(CPACK_GENERATOR TGZ) -+ include(CPack) -+ set(CMAKE_INSTALL_PACKAGEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) -+ install(EXPORT OpusTargets -+ NAMESPACE Opus:: -+ DESTINATION ${CMAKE_INSTALL_PACKAGEDIR}) -+ -+ include(CMakePackageConfigHelpers) -+ -+ set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}) -+ configure_package_config_file( -+ ${PROJECT_SOURCE_DIR}/cmake/OpusConfig.cmake.in -+ OpusConfig.cmake -+ INSTALL_DESTINATION -+ ${CMAKE_INSTALL_PACKAGEDIR} -+ PATH_VARS -+ INCLUDE_INSTALL_DIR -+ INSTALL_PREFIX -+ ${CMAKE_INSTALL_PREFIX}) -+ -+ write_basic_package_version_file(OpusConfigVersion.cmake -+ VERSION ${PROJECT_VERSION} -+ COMPATIBILITY SameMajorVersion) -+ -+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpusConfig.cmake -+ ${CMAKE_CURRENT_BINARY_DIR}/OpusConfigVersion.cmake -+ DESTINATION ${CMAKE_INSTALL_PACKAGEDIR}) - endif() -- configure_file(opus.pc.in opus.pc) -- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/opus.pc -- DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) --endif() -- --if(OPUS_INSTALL_CMAKE_CONFIG_MODULE) -- set(CPACK_GENERATOR TGZ) -- include(CPack) -- set(CMAKE_INSTALL_PACKAGEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) -- install(EXPORT OpusTargets -- NAMESPACE Opus:: -- DESTINATION ${CMAKE_INSTALL_PACKAGEDIR}) -- -- include(CMakePackageConfigHelpers) -- -- set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}) -- configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/OpusConfig.cmake.in -- OpusConfig.cmake -- INSTALL_DESTINATION -- ${CMAKE_INSTALL_PACKAGEDIR} -- PATH_VARS -- INCLUDE_INSTALL_DIR -- INSTALL_PREFIX -- ${CMAKE_INSTALL_PREFIX}) -- write_basic_package_version_file(OpusConfigVersion.cmake -- VERSION ${PROJECT_VERSION} -- COMPATIBILITY SameMajorVersion) -- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpusConfig.cmake -- ${CMAKE_CURRENT_BINARY_DIR}/OpusConfigVersion.cmake -- DESTINATION ${CMAKE_INSTALL_PACKAGEDIR}) - endif() - - if(OPUS_BUILD_PROGRAMS) diff --git a/CMakeLists.txt b/CMakeLists.txt index d26063b8b9..3dacc6b366 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -464,21 +464,6 @@ if (NOT YUZU_STATIC_ROOM) if (ZLIB_ADDED) add_library(ZLIB::ZLIB ALIAS zlibstatic) endif() - - # Opus - AddJsonPackage(opus) - - if (Opus_ADDED) - if (MSVC AND CXX_CLANG) - target_compile_options(opus PRIVATE - $<$:-Wno-implicit-function-declaration> - ) - endif() - endif() - - if (NOT TARGET Opus::opus) - add_library(Opus::opus ALIAS opus) - endif() endif() if(NOT TARGET Boost::headers) diff --git a/CMakeModules/find/FindOpus.cmake b/CMakeModules/find/FindOpus.cmake deleted file mode 100644 index a4247f06db..0000000000 --- a/CMakeModules/find/FindOpus.cmake +++ /dev/null @@ -1,22 +0,0 @@ -# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - -# SPDX-FileCopyrightText: 2022 yuzu Emulator Project -# SPDX-License-Identifier: GPL-2.0-or-later - -find_package(PkgConfig QUIET) -pkg_search_module(OPUS QUIET IMPORTED_TARGET opus) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Opus - REQUIRED_VARS OPUS_LINK_LIBRARIES - VERSION_VAR OPUS_VERSION -) - -if (PLATFORM_MSYS) - FixMsysPath(PkgConfig::OPUS) -endif() - -if (Opus_FOUND AND NOT TARGET Opus::opus) - add_library(Opus::opus ALIAS PkgConfig::OPUS) -endif() diff --git a/cpmfile.json b/cpmfile.json index cac6c357b0..735b145cc9 100644 --- a/cpmfile.json +++ b/cpmfile.json @@ -218,21 +218,6 @@ "repo": "jimmy-park/openssl-cmake", "version": "3.6.2" }, - "opus": { - "find_args": "MODULE", - "hash": "9506147b0de35befda8633ff272981cc2575c860874791bd455b752f797fd7dbd1079f0ba42ccdd7bb1fe6773fa5e84b3d75667c2883dd1fb2d0e4a5fa4f8387", - "min_version": "1.3", - "options": [ - "OPUS_PRESUME_NEON ON" - ], - "package": "Opus", - "patches": [ - "0001-disable-clang-runtime-neon.patch", - "0002-no-install.patch" - ], - "repo": "xiph/opus", - "version": "a3f0ec02b3" - }, "quazip": { "hash": "609c240c7f029ac26a37d8fbab51bc16284e05e128b78b9b9c0e95d083538c36047a67d682759ac990e4adb0eeb90f04f1ea7fe2253bbda7e7e3bcce32e53dd8", "min_version": "1.3", diff --git a/docs/Deps.md b/docs/Deps.md index 612e423d9f..39330ec22c 100644 --- a/docs/Deps.md +++ b/docs/Deps.md @@ -354,7 +354,7 @@ pacman -Syuu --needed --noconfirm $packages HaikuOS ```sh -pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel +pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel ``` [Caveats](./Caveats.md#haikuos). diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt index 32b72fe38f..226edfa00c 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt @@ -79,13 +79,6 @@ class LicensesFragment : Fragment() { R.string.license_ffmpeg_copyright, R.string.license_ffmpeg_text ), - License( - R.string.license_opus, - R.string.license_opus_description, - R.string.license_opus_link, - R.string.license_opus_copyright, - R.string.license_opus_text - ), License( R.string.license_sirit, R.string.license_sirit_description, diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index c67c2f279f..f0072b4267 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -1694,51 +1694,6 @@ RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - - Opus - Modern audio compression for the internet - https://github.com/xiph/opus - Copyright 2001–2011 Xiph.Org, Skype Limited, Octasic, Jean-Marc Valin, Timothy B. Terriberry, CSIRO, Gregory Maxwell, Mark Borgerding, Erik de Castro Lopo - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met:\n\n - -- Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer.\n\n - -- Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution.\n\n - -- Neither the name of Internet Society, IETF or IETF Trust, nor the -names of specific contributors, may be used to endorse or promote -products derived from this software without specific prior written -permission.\n\n - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -``AS IS\'\' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER -OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n - -Opus is subject to the royalty-free patent licenses which are -specified at:\n\n - -Xiph.Org Foundation: -https://datatracker.ietf.org/ipr/1524/ \n\n - -Microsoft Corporation: -https://datatracker.ietf.org/ipr/1914/ \n\n - -Broadcom Corporation: -https://datatracker.ietf.org/ipr/1526/ Sirit A runtime SPIR-V assembler diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 4d0b130afc..a85b4b5a70 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -227,7 +227,7 @@ else() endif() target_include_directories(audio_core PRIVATE ${OPUS_INCLUDE_DIRS}) -target_link_libraries(audio_core PUBLIC common core Opus::opus) +target_link_libraries(audio_core PUBLIC common core) if (ENABLE_CUBEB) target_sources(audio_core PRIVATE diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp index e2b9eb566c..f5d862396f 100644 --- a/src/audio_core/adsp/apps/opus/opus_decode_object.cpp +++ b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp @@ -1,107 +1,71 @@ +// 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 +extern "C" { +#include +#include +#include +#include +#include +} #include "audio_core/adsp/apps/opus/opus_decode_object.h" +#include "audio_core/adsp/apps/opus/opus_types.h" #include "common/assert.h" +#include "core/hle/service/audio/errors.h" namespace AudioCore::ADSP::OpusDecoder { -namespace { -bool IsValidChannelCount(u32 channel_count) { - return channel_count == 1 || channel_count == 2; -} -} // namespace u32 OpusDecodeObject::GetWorkBufferSize(u32 channel_count) { - if (!IsValidChannelCount(channel_count)) { - return 0; - } - return static_cast(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count); + if (channel_count == 1 || channel_count == 2) + return u32(sizeof(OpusDecodeObject)) + 16 * channel_count; + return 0; } -OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) { - auto* new_decoder = reinterpret_cast(buffer); - auto* comparison = reinterpret_cast(buffer2); - - if (new_decoder->magic == DecodeObjectMagic) { - if (!new_decoder->initialized || - (new_decoder->initialized && new_decoder->self == comparison)) { - new_decoder->state_valid = true; - } - } else { - new_decoder->initialized = false; - new_decoder->state_valid = true; - } - return *new_decoder; +Result OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) { + if ((codec = avcodec_find_decoder(AV_CODEC_ID_OPUS)) == nullptr) + return Service::Audio::ResultLibOpusInvalidState; + if ((avctx = avcodec_alloc_context3(codec)) == nullptr) + return Service::Audio::ResultLibOpusInvalidState; + avctx->sample_rate = sample_rate; + av_channel_layout_default(&avctx->ch_layout, channel_count); + return ResultSuccess; } -s32 OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) { - if (!state_valid) { - return OPUS_INVALID_STATE; - } - - if (initialized) { - return OPUS_OK; - } - - // Unfortunately libopus does not expose the OpusDecoder struct publicly, so we can't include - // it in this class. Nintendo does not allocate memory, which is why we have a workbuffer - // provided. - // We could use _create and have libopus allocate it for us, but then we have to separately - // track which decoder is being used between this and multistream in order to call the correct - // destroy from the host side. - // This is a bit cringe, but is safe as these objects are only ever initialized inside the given - // workbuffer, and GetWorkBufferSize will guarantee there's enough space to follow. - decoder = (LibOpusDecoder*)(this + 1); - s32 ret = opus_decoder_init(decoder, sample_rate, channel_count); - if (ret == OPUS_OK) { - magic = DecodeObjectMagic; - initialized = true; - state_valid = true; - self = this; - final_range = 0; - } - return ret; +Result OpusDecodeObject::Shutdown() { + if (avctx) + avcodec_free_context(&avctx); + return ResultSuccess; } -s32 OpusDecodeObject::Shutdown() { - if (!state_valid) { - return OPUS_INVALID_STATE; - } - - if (initialized) { - magic = 0x0; - initialized = false; - state_valid = false; - self = nullptr; - final_range = 0; - decoder = nullptr; +Result OpusDecodeObject::ResetDecoder() { + if (avctx) { + avcodec_flush_buffers(avctx); + return ResultSuccess; } - return OPUS_OK; -} - -s32 OpusDecodeObject::ResetDecoder() { - return opus_decoder_ctl(decoder, OPUS_RESET_STATE); + return Service::Audio::ResultLibOpusInvalidState; } -s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, - u64 input_data, u64 input_data_size) { - ASSERT(initialized); +Result OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size) { out_sample_count = 0; - - if (!state_valid) { - return OPUS_INVALID_STATE; + if (avctx) { + AVPacket* avpkt = av_packet_alloc(); + av_new_packet(avpkt, int(input_data_size)); + std::memcpy(avpkt->data, reinterpret_cast(input_data), input_data_size); + avcodec_send_packet(avctx, avpkt); + + AVFrame* frame = av_frame_alloc(); + avcodec_receive_frame(avctx, frame); + std::memcpy(reinterpret_cast(output_data), frame->data, output_data_size); + av_frame_free(&frame); + av_packet_free(&avpkt); + + out_sample_count = frame->nb_samples; + return ResultSuccess; } - - auto ret_code_or_samples = opus_decode( - decoder, reinterpret_cast(input_data), static_cast(input_data_size), - reinterpret_cast(output_data), static_cast(output_data_size), 0); - - if (ret_code_or_samples < OPUS_OK) { - return ret_code_or_samples; - } - - out_sample_count = ret_code_or_samples; - return opus_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range); + return Service::Audio::ResultLibOpusInvalidState; } } // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.h b/src/audio_core/adsp/apps/opus/opus_decode_object.h index 6425f987c6..64a88f5a8d 100644 --- a/src/audio_core/adsp/apps/opus/opus_decode_object.h +++ b/src/audio_core/adsp/apps/opus/opus_decode_object.h @@ -1,38 +1,31 @@ +// 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 "common/common_types.h" +#include "audio_core/adsp/apps/opus/opus_types.h" +#include "core/hle/result.h" + +struct AVCodec; +struct AVCodecContext; namespace AudioCore::ADSP::OpusDecoder { -using LibOpusDecoder = ::OpusDecoder; -static constexpr u32 DecodeObjectMagic = 0xDEADBEEF; class OpusDecodeObject { public: static u32 GetWorkBufferSize(u32 channel_count); - static OpusDecodeObject& Initialize(u64 buffer, u64 buffer2); - - s32 InitializeDecoder(u32 sample_rate, u32 channel_count); - s32 Shutdown(); - s32 ResetDecoder(); - s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, - u64 input_data_size); - u32 GetFinalRange() const noexcept { - return final_range; - } - -private: - u32 magic; - bool initialized; - bool state_valid; - OpusDecodeObject* self; - u32 final_range; - LibOpusDecoder* decoder; + + Result InitializeDecoder(u32 sample_rate, u32 channel_count); + Result Shutdown(); + Result ResetDecoder(); + Result Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size); + + AVCodec const* codec = nullptr; + AVCodecContext* avctx = nullptr; }; -static_assert(std::is_trivially_constructible_v); } // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.cpp b/src/audio_core/adsp/apps/opus/opus_decoder.cpp index 8a009c04c7..03635ad4d4 100644 --- a/src/audio_core/adsp/apps/opus/opus_decoder.cpp +++ b/src/audio_core/adsp/apps/opus/opus_decoder.cpp @@ -16,6 +16,7 @@ #include "common/thread.h" #include "core/core.h" #include "core/core_timing.h" +#include "core/hle/service/audio/errors.h" namespace AudioCore::ADSP::OpusDecoder { @@ -49,11 +50,16 @@ OpusDecoder::~OpusDecoder() { // Shutdown the thread Send(Direction::DSP, Message::Shutdown); auto msg = Receive(Direction::Host); - ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", - Message::ShutdownOK, msg); + ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", Message::ShutdownOK, msg); main_thread.request_stop(); main_thread.join(); running = false; + + // Must shutdown as there are AV allocations which are manual + for (auto& e : decode_objects) + e.second.Shutdown(); + for (auto& e : ms_decode_objects) + e.second.Shutdown(); } void OpusDecoder::Send(Direction dir, u32 message) { @@ -68,202 +74,195 @@ void OpusDecoder::Init(std::stop_token stop_token) { Common::SetCurrentThreadName("DSP_OpusDecoder_Init"); if (Receive(Direction::DSP, stop_token) != Message::Start) { - LOG_ERROR(Service_Audio, - "DSP OpusDecoder failed to receive Start message. Opus initialization failed."); + LOG_ERROR(Service_Audio, "DSP OpusDecoder failed to receive Start message. Opus initialization failed."); return; } - main_thread = std::jthread([this](std::stop_token st) { Main(st); }); - running = true; - Send(Direction::Host, Message::StartOK); -} - -void OpusDecoder::Main(std::stop_token stop_token) { - Common::SetCurrentThreadName("DSP_OpusDecoder_Main"); - - while (!stop_token.stop_requested()) { - auto msg = Receive(Direction::DSP, stop_token); - switch (msg) { - case Shutdown: - Send(Direction::Host, Message::ShutdownOK); - return; - - case GetWorkBufferSize: { - auto channel_count = static_cast(shared_memory->host_send_data[0]); - - ASSERT(IsValidChannelCount(channel_count)); - - shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count); - Send(Direction::Host, Message::GetWorkBufferSizeOK); - } break; - - case InitializeDecodeObject: { - auto buffer = shared_memory->host_send_data[0]; - auto buffer_size = shared_memory->host_send_data[1]; - auto sample_rate = static_cast(shared_memory->host_send_data[2]); - auto channel_count = static_cast(shared_memory->host_send_data[3]); - - ASSERT(sample_rate >= 0); - ASSERT(IsValidChannelCount(channel_count)); - ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count)); - - auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); - shared_memory->dsp_return_data[0] = - decoder_object.InitializeDecoder(sample_rate, channel_count); - - Send(Direction::Host, Message::InitializeDecodeObjectOK); - } break; - - case ShutdownDecodeObject: { - auto buffer = shared_memory->host_send_data[0]; - [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; - - auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); - shared_memory->dsp_return_data[0] = decoder_object.Shutdown(); - - Send(Direction::Host, Message::ShutdownDecodeObjectOK); - } break; - - case DecodeInterleaved: { - auto start_time = system.CoreTiming().GetGlobalTimeUs(); - - auto buffer = shared_memory->host_send_data[0]; - auto input_data = shared_memory->host_send_data[1]; - auto input_data_size = shared_memory->host_send_data[2]; - auto output_data = shared_memory->host_send_data[3]; - auto output_data_size = shared_memory->host_send_data[4]; - auto final_range = static_cast(shared_memory->host_send_data[5]); - auto reset_requested = shared_memory->host_send_data[6]; - - u32 decoded_samples{0}; - - auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); - s32 error_code{OPUS_OK}; - if (reset_requested) { - error_code = decoder_object.ResetDecoder(); + // Main OpusDecoder thread, responsible for processing the incoming Opus packets. + main_thread = std::jthread([this](std::stop_token stop_token) { + Common::SetCurrentThreadName("DSP_OpusDecoder_Main"); + while (!stop_token.stop_requested()) { + auto msg = Receive(Direction::DSP, stop_token); + switch (msg) { + case Shutdown: + Send(Direction::Host, Message::ShutdownOK); + return; + case GetWorkBufferSize: { + auto channel_count = s32(shared_memory->host_send_data[0]); + + ASSERT(IsValidChannelCount(channel_count)); + + shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count); + Send(Direction::Host, Message::GetWorkBufferSizeOK); + break; } - - if (error_code == OPUS_OK) { - error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size, - input_data, input_data_size); + case InitializeDecodeObject: { + auto buffer = shared_memory->host_send_data[0]; + auto buffer_size = shared_memory->host_send_data[1]; + auto sample_rate = s32(shared_memory->host_send_data[2]); + auto channel_count = s32(shared_memory->host_send_data[3]); + + ASSERT(sample_rate >= 0); + ASSERT(IsValidChannelCount(channel_count)); + ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count)); + + if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) { + it->second.Shutdown(); + shared_memory->dsp_return_data[0] = it->second.InitializeDecoder(sample_rate, channel_count).raw; + } else { + OpusDecodeObject obj{}; + shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, channel_count).raw; + decode_objects.insert_or_assign(buffer, obj); + } + Send(Direction::Host, Message::InitializeDecodeObjectOK); + break; } - - if (error_code == OPUS_OK) { - if (final_range && decoder_object.GetFinalRange() != final_range) { - error_code = OPUS_INVALID_PACKET; + case ShutdownDecodeObject: { + auto buffer = shared_memory->host_send_data[0]; + //[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; + if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) { + shared_memory->dsp_return_data[0] = it->second.Shutdown().raw; + } else { + LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer); + shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw; } + Send(Direction::Host, Message::ShutdownDecodeObjectOK); + break; } - - auto end_time = system.CoreTiming().GetGlobalTimeUs(); - shared_memory->dsp_return_data[0] = error_code; - shared_memory->dsp_return_data[1] = decoded_samples; - shared_memory->dsp_return_data[2] = (end_time - start_time).count(); - - Send(Direction::Host, Message::DecodeInterleavedOK); - } break; - - case MapMemory: { - [[maybe_unused]] auto buffer = shared_memory->host_send_data[0]; - [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; - Send(Direction::Host, Message::MapMemoryOK); - } break; - - case UnmapMemory: { - [[maybe_unused]] auto buffer = shared_memory->host_send_data[0]; - [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; - Send(Direction::Host, Message::UnmapMemoryOK); - } break; - - case GetWorkBufferSizeForMultiStream: { - auto total_stream_count = static_cast(shared_memory->host_send_data[0]); - auto stereo_stream_count = static_cast(shared_memory->host_send_data[1]); - - ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count)); - - shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize( - total_stream_count, stereo_stream_count); - Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK); - } break; - - case InitializeMultiStreamDecodeObject: { - auto buffer = shared_memory->host_send_data[0]; - auto buffer_size = shared_memory->host_send_data[1]; - auto sample_rate = static_cast(shared_memory->host_send_data[2]); - auto channel_count = static_cast(shared_memory->host_send_data[3]); - auto total_stream_count = static_cast(shared_memory->host_send_data[4]); - auto stereo_stream_count = static_cast(shared_memory->host_send_data[5]); - // Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel - // mappings, but [6] is never set, and there is not enough room in the argument data for - // more than 40 channels, when 255 are possible. - // It also means the mapping values are undefined, though likely always 0, - // and the mappings given by the game are ignored. The mappings are copied to this - // dedicated buffer host side, so let's do as intended. - auto mappings = shared_memory->channel_mapping.data(); - - ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count)); - ASSERT(sample_rate >= 0); - ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize( - total_stream_count, stereo_stream_count)); - - auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); - shared_memory->dsp_return_data[0] = decoder_object.InitializeDecoder( - sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings); - - Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK); - } break; - - case ShutdownMultiStreamDecodeObject: { - auto buffer = shared_memory->host_send_data[0]; - [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; - - auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); - shared_memory->dsp_return_data[0] = decoder_object.Shutdown(); - - Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK); - } break; - - case DecodeInterleavedForMultiStream: { - auto start_time = system.CoreTiming().GetGlobalTimeUs(); - - auto buffer = shared_memory->host_send_data[0]; - auto input_data = shared_memory->host_send_data[1]; - auto input_data_size = shared_memory->host_send_data[2]; - auto output_data = shared_memory->host_send_data[3]; - auto output_data_size = shared_memory->host_send_data[4]; - auto final_range = static_cast(shared_memory->host_send_data[5]); - auto reset_requested = shared_memory->host_send_data[6]; - - u32 decoded_samples{0}; - - auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); - s32 error_code{OPUS_OK}; - if (reset_requested) { - error_code = decoder_object.ResetDecoder(); + case DecodeInterleaved: { + auto start_time = system.CoreTiming().GetGlobalTimeUs(); + + auto buffer = shared_memory->host_send_data[0]; + auto input_data = shared_memory->host_send_data[1]; + auto input_data_size = shared_memory->host_send_data[2]; + auto output_data = shared_memory->host_send_data[3]; + auto output_data_size = shared_memory->host_send_data[4]; + //auto final_range = static_cast(shared_memory->host_send_data[5]); + auto reset_requested = shared_memory->host_send_data[6]; + + u32 decoded_samples{0}; + + if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) { + auto res = ResultSuccess; + if (reset_requested) + res = it->second.ResetDecoder(); + if (res == ResultSuccess) + res = it->second.Decode(decoded_samples, output_data, output_data_size, input_data, input_data_size); + + auto end_time = system.CoreTiming().GetGlobalTimeUs(); + shared_memory->dsp_return_data[0] = res.raw; + shared_memory->dsp_return_data[1] = decoded_samples; + shared_memory->dsp_return_data[2] = (end_time - start_time).count(); + } else { + LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer); + shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw; + } + Send(Direction::Host, Message::DecodeInterleavedOK); + break; } - - if (error_code == OPUS_OK) { - error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size, - input_data, input_data_size); + case MapMemory: { + [[maybe_unused]] auto buffer = shared_memory->host_send_data[0]; + [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; + Send(Direction::Host, Message::MapMemoryOK); + break; } - - if (error_code == OPUS_OK) { - if (final_range && decoder_object.GetFinalRange() != final_range) { - error_code = OPUS_INVALID_PACKET; - } + case UnmapMemory: { + [[maybe_unused]] auto buffer = shared_memory->host_send_data[0]; + [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; + Send(Direction::Host, Message::UnmapMemoryOK); + break; } + case GetWorkBufferSizeForMultiStream: { + auto total_stream_count = s32(shared_memory->host_send_data[0]); + auto stereo_stream_count = s32(shared_memory->host_send_data[1]); - auto end_time = system.CoreTiming().GetGlobalTimeUs(); - shared_memory->dsp_return_data[0] = error_code; - shared_memory->dsp_return_data[1] = decoded_samples; - shared_memory->dsp_return_data[2] = (end_time - start_time).count(); + ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count)); - Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK); - } break; - - default: - LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg); - continue; + shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize( + total_stream_count, stereo_stream_count); + Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK); + break; + } + case InitializeMultiStreamDecodeObject: { + auto buffer = shared_memory->host_send_data[0]; + auto buffer_size = shared_memory->host_send_data[1]; + auto sample_rate = s32(shared_memory->host_send_data[2]); + auto channel_count = s32(shared_memory->host_send_data[3]); + auto total_stream_count = s32(shared_memory->host_send_data[4]); + auto stereo_stream_count = s32(shared_memory->host_send_data[5]); + // Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel + // mappings, but [6] is never set, and there is not enough room in the argument data for + // more than 40 channels, when 255 are possible. + // It also means the mapping values are undefined, though likely always 0, + // and the mappings given by the game are ignored. The mappings are copied to this + // dedicated buffer host side, so let's do as intended. + auto mappings = shared_memory->channel_mapping.data(); + + ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count)); + ASSERT(sample_rate >= 0); + ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize(total_stream_count, stereo_stream_count)); + + if (auto const it = ms_decode_objects.find(buffer); it != ms_decode_objects.end()) { + it->second.Shutdown(); + shared_memory->dsp_return_data[0] = it->second.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw; + } else { + OpusMultiStreamDecodeObject obj{}; + shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw; + ms_decode_objects.insert_or_assign(buffer, obj); + } + Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK); + break; + } + case ShutdownMultiStreamDecodeObject: { + auto buffer = shared_memory->host_send_data[0]; + //[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; + if (auto const it = ms_decode_objects.find(buffer); it != ms_decode_objects.end()) { + shared_memory->dsp_return_data[0] = it->second.Shutdown().raw; + } else { + LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer); + shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw; + } + Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK); + break; + } + case DecodeInterleavedForMultiStream: { + auto start_time = system.CoreTiming().GetGlobalTimeUs(); + + auto buffer = shared_memory->host_send_data[0]; + auto input_data = shared_memory->host_send_data[1]; + auto input_data_size = shared_memory->host_send_data[2]; + auto output_data = shared_memory->host_send_data[3]; + auto output_data_size = shared_memory->host_send_data[4]; + //auto final_range = static_cast(shared_memory->host_send_data[5]); + auto reset_requested = shared_memory->host_send_data[6]; + + u32 decoded_samples{0}; + + if (auto const it = ms_decode_objects.find(buffer); it != ms_decode_objects.end()) { + auto res = ResultSuccess; + if (reset_requested) + res = it->second.ResetDecoder(); + if (res == ResultSuccess) + res = it->second.Decode(decoded_samples, output_data, output_data_size, input_data, input_data_size); + + auto end_time = system.CoreTiming().GetGlobalTimeUs(); + shared_memory->dsp_return_data[0] = res.raw; + shared_memory->dsp_return_data[1] = decoded_samples; + shared_memory->dsp_return_data[2] = (end_time - start_time).count(); + } else { + LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer); + shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw; + } + Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK); + break; + } + default: + LOG_ERROR(Audio_DSP, "Invalid OpusDecoder command {}", msg); + continue; + } } - } + }); + running = true; + Send(Direction::Host, Message::StartOK); } } // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.h b/src/audio_core/adsp/apps/opus/opus_decoder.h index 5b3f22bf4c..7cdac1da51 100644 --- a/src/audio_core/adsp/apps/opus/opus_decoder.h +++ b/src/audio_core/adsp/apps/opus/opus_decoder.h @@ -9,6 +9,9 @@ #include #include +#include "ankerl/unordered_dense.h" +#include "audio_core/adsp/apps/opus/opus_decode_object.h" +#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h" #include "audio_core/adsp/apps/opus/shared_memory.h" #include "audio_core/adsp/mailbox.h" #include "common/common_types.h" @@ -68,9 +71,7 @@ public: } private: - /** - * Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread. - */ + /// @brief Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread. void Init(std::stop_token stop_token); /** * Main OpusDecoder thread, responsible for processing the incoming Opus packets. @@ -90,6 +91,9 @@ private: /// Structure shared with the host, input data set by the host before sending a mailbox message, /// and the responses are written back by the OpusDecoder. SharedMemory* shared_memory{}; + + ankerl::unordered_dense::map decode_objects; + ankerl::unordered_dense::map ms_decode_objects; }; } // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp index 05cf3975db..6f5669e316 100644 --- a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp +++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp @@ -1,8 +1,20 @@ +// 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 +extern "C" { +#include +#include +#include +#include +#include +} #include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h" #include "common/assert.h" +#include "core/hle/result.h" +#include "core/hle/service/audio/errors.h" namespace AudioCore::ADSP::OpusDecoder { @@ -12,100 +24,59 @@ bool IsValidChannelCount(u32 channel_count) { } bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) { - return total_stream_count > 0 && static_cast(stereo_stream_count) >= 0 && - stereo_stream_count <= total_stream_count && IsValidChannelCount(total_stream_count); + return total_stream_count > 0 && s32(stereo_stream_count) >= 0 + && stereo_stream_count <= total_stream_count && IsValidChannelCount(total_stream_count); } } // namespace -u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count, - u32 stereo_stream_count) { - if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) { - return static_cast(sizeof(OpusMultiStreamDecodeObject)) + - opus_multistream_decoder_get_size(total_stream_count, stereo_stream_count); - } +u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count) { + if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) + return u32(sizeof(OpusMultiStreamDecodeObject)) + 2556 * (total_stream_count * stereo_stream_count); return 0; } -OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) { - auto* new_decoder = reinterpret_cast(buffer); - auto* comparison = reinterpret_cast(buffer2); - - if (new_decoder->magic == DecodeMultiStreamObjectMagic) { - if (!new_decoder->initialized || - (new_decoder->initialized && new_decoder->self == comparison)) { - new_decoder->state_valid = true; - } - } else { - new_decoder->initialized = false; - new_decoder->state_valid = true; - } - return *new_decoder; +Result OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, u32 stereo_stream_count, u8* mappings) { + if ((codec = avcodec_find_decoder(AV_CODEC_ID_OPUS)) == nullptr) + return Service::Audio::ResultLibOpusInvalidState; + if ((avctx = avcodec_alloc_context3(codec))) + return Service::Audio::ResultLibOpusInvalidState; + avctx->sample_rate = sample_rate; + av_channel_layout_default(&avctx->ch_layout, channel_count); + return ResultSuccess; } -s32 OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count, - u32 channel_count, u32 stereo_stream_count, - u8* mappings) { - if (!state_valid) { - return OPUS_INVALID_STATE; - } - - if (initialized) { - return OPUS_OK; - } - - // See OpusDecodeObject::InitializeDecoder for an explanation of this - decoder = (LibOpusMSDecoder*)(this + 1); - s32 ret = opus_multistream_decoder_init(decoder, sample_rate, channel_count, total_stream_count, - stereo_stream_count, mappings); - if (ret == OPUS_OK) { - magic = DecodeMultiStreamObjectMagic; - initialized = true; - state_valid = true; - self = this; - final_range = 0; - } - return ret; +Result OpusMultiStreamDecodeObject::Shutdown() { + if (avctx) + avcodec_free_context(&avctx); + return ResultSuccess; } -s32 OpusMultiStreamDecodeObject::Shutdown() { - if (!state_valid) { - return OPUS_INVALID_STATE; +Result OpusMultiStreamDecodeObject::ResetDecoder() { + if (avctx) { + avcodec_flush_buffers(avctx); + return ResultSuccess; } - - if (initialized) { - magic = 0x0; - initialized = false; - state_valid = false; - self = nullptr; - final_range = 0; - decoder = nullptr; - } - return OPUS_OK; + return Service::Audio::ResultLibOpusInvalidState; } -s32 OpusMultiStreamDecodeObject::ResetDecoder() { - return opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE); -} - -s32 OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data, - u64 output_data_size, u64 input_data, u64 input_data_size) { - ASSERT(initialized); +Result OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size) { out_sample_count = 0; - - if (!state_valid) { - return OPUS_INVALID_STATE; + if (avctx) { + AVPacket* avpkt = av_packet_alloc(); + av_new_packet(avpkt, int(input_data_size)); + std::memcpy(avpkt->data, reinterpret_cast(input_data), input_data_size); + avcodec_send_packet(avctx, avpkt); + + AVFrame* frame = av_frame_alloc(); + avcodec_receive_frame(avctx, frame); + std::memcpy(reinterpret_cast(output_data), frame->data, output_data_size); + av_frame_free(&frame); + av_packet_free(&avpkt); + + out_sample_count = frame->nb_samples; + return ResultSuccess; } - - auto ret_code_or_samples = opus_multistream_decode( - decoder, reinterpret_cast(input_data), static_cast(input_data_size), - reinterpret_cast(output_data), static_cast(output_data_size), 0); - - if (ret_code_or_samples < OPUS_OK) { - return ret_code_or_samples; - } - - out_sample_count = ret_code_or_samples; - return opus_multistream_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range); + return Service::Audio::ResultLibOpusInvalidState; } } // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h index 93558ded5d..452b49e7a3 100644 --- a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h +++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h @@ -1,39 +1,31 @@ +// 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 "audio_core/adsp/apps/opus/opus_types.h" #include "common/common_types.h" +#include "core/hle/result.h" + +struct AVCodec; +struct AVCodecContext; namespace AudioCore::ADSP::OpusDecoder { -using LibOpusMSDecoder = ::OpusMSDecoder; -static constexpr u32 DecodeMultiStreamObjectMagic = 0xDEADBEEF; class OpusMultiStreamDecodeObject { public: static u32 GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count); - static OpusMultiStreamDecodeObject& Initialize(u64 buffer, u64 buffer2); - - s32 InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, - u32 stereo_stream_count, u8* mappings); - s32 Shutdown(); - s32 ResetDecoder(); - s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, - u64 input_data_size); - u32 GetFinalRange() const noexcept { - return final_range; - } - -private: - u32 magic; - bool initialized; - bool state_valid; - OpusMultiStreamDecodeObject* self; - u32 final_range; - LibOpusMSDecoder* decoder; + + Result InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, u32 stereo_stream_count, u8* mappings); + Result Shutdown(); + Result ResetDecoder(); + Result Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size); + + AVCodec const* codec = nullptr; + AVCodecContext* avctx = nullptr; }; -static_assert(std::is_trivially_constructible_v); } // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/opus_types.h b/src/audio_core/adsp/apps/opus/opus_types.h new file mode 100644 index 0000000000..bacfb30338 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_types.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "common/common_types.h" + +namespace AudioCore::ADSP { + +static constexpr u32 DECODE_OBJECT_MAGIC = 0xDEADBEEF; +struct LibOpusDecoder { + u32 magic; + bool initialized; + bool state_valid; + LibOpusDecoder* self; + u32 final_range; + void* decoder; +}; +static_assert(sizeof(LibOpusDecoder) == 32); + +static constexpr u32 DECODE_MULTISTREAM_OBJECT_MAGIC = 0xDEADBEEF; +struct LibOpusMultistreamDecoder { + u32 magic; + bool initialized; + bool state_valid; + LibOpusMultistreamDecoder* self; + u32 final_range; + void* decoder; +}; +static_assert(sizeof(LibOpusMultistreamDecoder) == 32); + +} diff --git a/src/audio_core/opus/hardware_opus.cpp b/src/audio_core/opus/hardware_opus.cpp index 30805f4a36..4fdff2363d 100644 --- a/src/audio_core/opus/hardware_opus.cpp +++ b/src/audio_core/opus/hardware_opus.cpp @@ -1,3 +1,6 @@ +// 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 @@ -6,39 +9,18 @@ #include "audio_core/audio_core.h" #include "audio_core/opus/hardware_opus.h" #include "core/core.h" +#include "core/hle/result.h" namespace AudioCore::OpusDecoder { + namespace { using namespace Service::Audio; - -static constexpr Result ResultCodeFromLibOpusErrorCode(u64 error_code) { - s32 error{static_cast(error_code)}; - ASSERT(error <= OPUS_OK); - switch (error) { - case OPUS_ALLOC_FAIL: - R_THROW(ResultLibOpusAllocFail); - case OPUS_INVALID_STATE: - R_THROW(ResultLibOpusInvalidState); - case OPUS_UNIMPLEMENTED: - R_THROW(ResultLibOpusUnimplemented); - case OPUS_INVALID_PACKET: - R_THROW(ResultLibOpusInvalidPacket); - case OPUS_INTERNAL_ERROR: - R_THROW(ResultLibOpusInternalError); - case OPUS_BUFFER_TOO_SMALL: - R_THROW(ResultBufferTooSmall); - case OPUS_BAD_ARG: - R_THROW(ResultLibOpusBadArg); - case OPUS_OK: - R_RETURN(ResultSuccess); - } - UNREACHABLE(); -} - } // namespace HardwareOpus::HardwareOpus(Core::System& system_) - : system{system_}, opus_decoder{system.AudioCore().ADSP().OpusDecoder()} { + : system{system_} + , opus_decoder{system.AudioCore().ADSP().OpusDecoder()} +{ opus_decoder.SetSharedMemory(shared_memory); } @@ -89,7 +71,7 @@ Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count, R_THROW(ResultInvalidOpusDSPReturnCode); } - R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); + R_RETURN(Result(u32(shared_memory.dsp_return_data[0]))); } Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count, @@ -117,7 +99,7 @@ Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 chan R_THROW(ResultInvalidOpusDSPReturnCode); } - R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); + R_RETURN(Result(u32(shared_memory.dsp_return_data[0]))); } Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) { @@ -131,7 +113,7 @@ Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) { "Expected Opus shutdown code {}, got {}", ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg); - R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); + R_RETURN(Result(u32(shared_memory.dsp_return_data[0]))); } Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) { @@ -146,7 +128,7 @@ Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_si "Expected Opus shutdown code {}, got {}", ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg); - R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); + R_RETURN(Result(u32(shared_memory.dsp_return_data[0]))); } Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data, @@ -170,12 +152,12 @@ Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data, R_THROW(ResultInvalidOpusDSPReturnCode); } - auto error_code{static_cast(shared_memory.dsp_return_data[0])}; - if (error_code == OPUS_OK) { - out_sample_count = static_cast(shared_memory.dsp_return_data[1]); + auto error_code = s32(shared_memory.dsp_return_data[0]); + if (error_code == ResultSuccess.raw) { + out_sample_count = u32(shared_memory.dsp_return_data[1]); out_time_taken = 1000 * shared_memory.dsp_return_data[2]; } - R_RETURN(ResultCodeFromLibOpusErrorCode(error_code)); + R_RETURN(Result(u32(error_code))); } Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data, @@ -184,29 +166,27 @@ Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void void* buffer, u64& out_time_taken, bool reset) { std::scoped_lock l{mutex}; - shared_memory.host_send_data[0] = (u64)buffer; - shared_memory.host_send_data[1] = (u64)input_data; + shared_memory.host_send_data[0] = u64(buffer); + shared_memory.host_send_data[1] = u64(input_data); shared_memory.host_send_data[2] = input_data_size; - shared_memory.host_send_data[3] = (u64)output_data; + shared_memory.host_send_data[3] = u64(output_data); shared_memory.host_send_data[4] = output_data_size; shared_memory.host_send_data[5] = 0; shared_memory.host_send_data[6] = reset; - opus_decoder.Send(ADSP::Direction::DSP, - ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream); + opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream); auto msg = opus_decoder.Receive(ADSP::Direction::Host); if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) { - LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", - ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg); + LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg); R_THROW(ResultInvalidOpusDSPReturnCode); } - auto error_code{static_cast(shared_memory.dsp_return_data[0])}; - if (error_code == OPUS_OK) { + auto const error_code = shared_memory.dsp_return_data[0]; + if (error_code == ResultSuccess.raw) { out_sample_count = static_cast(shared_memory.dsp_return_data[1]); out_time_taken = 1000 * shared_memory.dsp_return_data[2]; } - R_RETURN(ResultCodeFromLibOpusErrorCode(error_code)); + R_RETURN(Result(u32(shared_memory.dsp_return_data[0]))); } Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) { @@ -217,8 +197,7 @@ Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) { opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory); auto msg = opus_decoder.Receive(ADSP::Direction::Host); if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) { - LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", - ADSP::OpusDecoder::Message::MapMemoryOK, msg); + LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::MapMemoryOK, msg); R_THROW(ResultInvalidOpusDSPReturnCode); } R_SUCCEED(); @@ -232,8 +211,7 @@ Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) { opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory); auto msg = opus_decoder.Receive(ADSP::Direction::Host); if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) { - LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", - ADSP::OpusDecoder::Message::UnmapMemoryOK, msg); + LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::UnmapMemoryOK, msg); R_THROW(ResultInvalidOpusDSPReturnCode); } R_SUCCEED(); diff --git a/src/audio_core/opus/hardware_opus.h b/src/audio_core/opus/hardware_opus.h index caa7468401..4b345e9b63 100644 --- a/src/audio_core/opus/hardware_opus.h +++ b/src/audio_core/opus/hardware_opus.h @@ -1,11 +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 "audio_core/adsp/apps/opus/opus_decoder.h" #include "audio_core/adsp/apps/opus/shared_memory.h" #include "audio_core/adsp/mailbox.h"