From ff8368c6066faf3962e2f77af7da4954e2ff6a43 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 12 Sep 2026 09:59:33 +0200 Subject: [PATCH] [audio] Replace libopus with FFmpeg (#4169) FFmpeg offers two opus decoders: - libopus, this one is fed the same data (that affects output) as the previous libopus decoder had as well - native opus decoder: this only takes Float32 samples, we then need to invoke swrescale to translate between s16 (what switch sends) and float32 (what FFmpeg expects), the process is computationally more expensive, but trades off neatly as the native opus decoder is very optimised It also relegates the handling to FFmpeg, which simplifies existing code (more concretely, by using the "fill/drain" scheme for packets/frames) Closes #2897 Signed-off-by: lizzie Co-authored-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4169 Reviewed-by: CamilleLaVey Reviewed-by: Maufeat --- .../0001-disable-clang-runtime-neon.patch | 28 - .patch/opus/0002-no-install.patch | 153 ----- CMakeLists.txt | 15 - CMakeModules/find/FindOpus.cmake | 22 - cpmfile.json | 17 +- docs/Deps.md | 19 +- shell.nix | 2 +- .../yuzu_emu/fragments/LicensesFragment.kt | 7 - .../app/src/main/res/values/strings.xml | 45 -- src/audio_core/CMakeLists.txt | 14 +- .../adsp/apps/opus/opus_decode_object.cpp | 107 ---- .../adsp/apps/opus/opus_decode_object.h | 38 -- .../adsp/apps/opus/opus_decoder.cpp | 587 +++++++++++------- src/audio_core/adsp/apps/opus/opus_decoder.h | 27 +- .../opus/opus_multistream_decode_object.cpp | 113 ---- .../opus/opus_multistream_decode_object.h | 39 -- src/audio_core/adsp/apps/opus/opus_types.h | 32 + src/audio_core/adsp/apps/opus/shared_memory.h | 4 +- src/audio_core/opus/hardware_opus.cpp | 71 +-- src/audio_core/opus/hardware_opus.h | 2 - src/core/CMakeLists.txt | 1 - src/video_core/CMakeLists.txt | 2 - 22 files changed, 441 insertions(+), 904 deletions(-) delete mode 100644 .patch/opus/0001-disable-clang-runtime-neon.patch delete mode 100644 .patch/opus/0002-no-install.patch delete mode 100644 CMakeModules/find/FindOpus.cmake delete mode 100644 src/audio_core/adsp/apps/opus/opus_decode_object.cpp delete mode 100644 src/audio_core/adsp/apps/opus/opus_decode_object.h delete mode 100644 src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp delete mode 100644 src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h create mode 100644 src/audio_core/adsp/apps/opus/opus_types.h 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 266331b0ac..f5c360b294 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 257642682e..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 (MSYS2) - 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 8b2099f40a..419c8d2d69 100644 --- a/cpmfile.json +++ b/cpmfile.json @@ -82,7 +82,7 @@ "name": "ffmpeg", "package": "FFmpeg", "repo": "crueter-ci/FFmpeg", - "version": "9.0.1-1788120736-bf1b838f2a" + "version": "9.0.1-1788303113-bf1b838f2a" }, "fmt": { "hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2", @@ -210,21 +210,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 2b73ecb4ac..9a781ebdcd 100644 --- a/docs/Deps.md +++ b/docs/Deps.md @@ -60,7 +60,6 @@ All other dependencies will be downloaded and built by [CPM](https://github.com/ * [ZLIB](https://www.zlib.net/) 1.2+ * [zstd](https://facebook.github.io/zstd/) 1.5+ * [enet](http://enet.bespin.org/) 1.3+ -* [Opus](https://opus-codec.org/) 1.3+ Vulkan 1.3.274+ is also needed: @@ -121,7 +120,7 @@ sudo emerge -a \ dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \ dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \ dev-util/vulkan-utility-libraries dev-util/glslang \ - media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \ + media-gfx/renderdoc media-libs/libva media-video/ffmpeg \ media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \ net-libs/enet \ sys-libs/zlib \ @@ -153,7 +152,7 @@ Required USE flags: Arch Linux ```sh -sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl opus qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers +sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers ``` * Building with QT Web Engine requires `qt6-webengine` as well. @@ -166,7 +165,7 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl Ubuntu, Debian, Mint Linux ```sh -sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev +sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libasound2t64 vulkan-utility-libraries-dev ``` * Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required. @@ -213,7 +212,7 @@ First, enable the community repository; [see here](https://wiki.alpinelinux.org/ # Enable the community repository setup-apkrepos -c # Install -apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch +apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev jq patch ``` @@ -261,7 +260,7 @@ brew install molten-vk As root run: ```sh -pkg install devel/cmake devel/sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base x11-toolkits/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib graphics/vulkan-utility-libraries graphics/vulkan-headers graphics/spirv-headers quazip-qt6 +pkg install devel/cmake devel/sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base x11-toolkits/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib graphics/vulkan-utility-libraries graphics/vulkan-headers graphics/spirv-headers quazip-qt6 ``` If using FreeBSD 12 or prior, use `devel/pkg-config` instead. @@ -275,7 +274,7 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead. For NetBSD +10.1: ```sh -pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6-qtbase qt6-qtcharts qt6-qtmultimedia qt6-qttools cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen +pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq qt6-qtbase qt6-qtcharts qt6-qtmultimedia qt6-qttools cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen ``` [Caveats](./Caveats.md#netbsd). @@ -306,7 +305,7 @@ pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmak OpenIndiana ```sh -sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt +sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt ``` [Caveats](./Caveats.md#openindiana). @@ -330,7 +329,7 @@ sudo pkgin install git cmake autoconf build-essential libusb-1 nasm gcc13 ```sh BASE="git make autoconf libtool automake-wrapper jq patch" -MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb openssl SDL3" +MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet libusb openssl SDL3" # Either x86_64 or clang-aarch64 (Windows on ARM) packages="$BASE" for pkg in $MINGW; do @@ -356,7 +355,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/shell.nix b/shell.nix index 786b94ccb2..e4d57d7e5e 100755 --- a/shell.nix +++ b/shell.nix @@ -12,7 +12,7 @@ pkgs.mkShellNoCC { git cmake clang gnumake patch jq pkg-config # libraries openssl boost fmt nlohmann_json lz4 zlib zstd - enet libopus vulkan-headers vulkan-utility-libraries + enet vulkan-headers vulkan-utility-libraries spirv-tools spirv-headers vulkan-loader unzip glslang python3 httplib cpp-jwt ffmpeg-headless libusb1 cubeb 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 a9200bbca9..ca7263ead7 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -1783,51 +1783,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 b244d657a9..71a46a7b1e 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -15,10 +15,6 @@ add_library(audio_core STATIC adsp/apps/audio_renderer/command_list_processor.h adsp/apps/opus/opus_decoder.cpp adsp/apps/opus/opus_decoder.h - adsp/apps/opus/opus_decode_object.cpp - adsp/apps/opus/opus_decode_object.h - adsp/apps/opus/opus_multistream_decode_object.cpp - adsp/apps/opus/opus_multistream_decode_object.h adsp/apps/opus/shared_memory.h audio_core.cpp audio_core.h @@ -226,8 +222,14 @@ else() $<$:-Wno-sign-conversion>) endif() -target_include_directories(audio_core PRIVATE ${OPUS_INCLUDE_DIRS}) -target_link_libraries(audio_core PUBLIC common core Opus::opus) +if (YUZU_USE_EXTERNAL_FFMPEG) + add_dependencies(audio_core ffmpeg-build) +endif() +target_include_directories(audio_core PUBLIC ${FFmpeg_INCLUDE_DIR}) +target_link_libraries(audio_core PRIVATE ${FFmpeg_LIBRARIES}) +target_link_options(audio_core PRIVATE ${FFmpeg_LDFLAGS}) + +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 deleted file mode 100644 index e2b9eb566c..0000000000 --- a/src/audio_core/adsp/apps/opus/opus_decode_object.cpp +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "audio_core/adsp/apps/opus/opus_decode_object.h" -#include "common/assert.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); -} - -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; -} - -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; -} - -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; - } - return OPUS_OK; -} - -s32 OpusDecodeObject::ResetDecoder() { - return opus_decoder_ctl(decoder, OPUS_RESET_STATE); -} - -s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, - u64 input_data, u64 input_data_size) { - ASSERT(initialized); - out_sample_count = 0; - - if (!state_valid) { - return OPUS_INVALID_STATE; - } - - 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); -} - -} // 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 deleted file mode 100644 index 6425f987c6..0000000000 --- a/src/audio_core/adsp/apps/opus/opus_decode_object.h +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include "common/common_types.h" - -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; -}; -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..0be0f774b1 100644 --- a/src/audio_core/adsp/apps/opus/opus_decoder.cpp +++ b/src/audio_core/adsp/apps/opus/opus_decoder.cpp @@ -5,55 +5,388 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include -#include "audio_core/adsp/apps/opus/opus_decode_object.h" -#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h" +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +} + #include "audio_core/adsp/apps/opus/shared_memory.h" #include "audio_core/audio_core.h" -#include "audio_core/common/common.h" #include "common/logging.h" #include "common/thread.h" #include "core/core.h" #include "core/core_timing.h" +#include "core/hle/service/audio/errors.h" namespace AudioCore::ADSP::OpusDecoder { namespace { -constexpr size_t OpusStreamCountMax = 255; +constexpr u32 OPUS_STREAM_COUNT_MAX = 255; +// https://git.ffmpeg.org/gitweb/ffmpeg.git/blob_plain/HEAD:/libavcodec/libopusdec.c +constexpr u32 OPUS_HEAD_SIZE = 19; +constexpr u32 OPUS_MAX_CHANNELS = 2; bool IsValidChannelCount(u32 channel_count) { - return channel_count == 1 || channel_count == 2; + return channel_count >= 1 || channel_count <= OPUS_MAX_CHANNELS; } -bool IsValidMultiStreamChannelCount(u32 channel_count) { - return channel_count <= OpusStreamCountMax; +bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) { + return total_stream_count > 0 && total_stream_count <= OPUS_STREAM_COUNT_MAX + && s32(stereo_stream_count) >= 0 && stereo_stream_count <= total_stream_count; } -bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 stereo_stream_count) { - return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 && - stereo_stream_count >= 0 && stereo_stream_count <= total_stream_count; -} +class OpusGenericDecodeObject { +public: + static u32 GetWorkBufferSizeMultistream(u32 total_stream_count, u32 stereo_stream_count) { + if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) + return 48 + 2556 * (total_stream_count * stereo_stream_count); + return 0; + } + + static u32 GetWorkBufferSize(u32 channel_count) { + if (channel_count == 1 || channel_count == 2) + return 48 + 16 * channel_count; + return 0; + } + + /// idempotency of initialize is guaranteed + Result InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, u32 stereo_stream_count, u8 const* mappings) { + // prefer libopus, ffmpeg docs say to use libopus **if** available + // However, native opus can also work with swrescale: + // it uses planarfloat, we can resample to s16 + AVCodec const* codec = avcodec_find_decoder_by_name("libopus"); + bool is_libopus = codec != nullptr; + if (!codec) { + LOG_WARNING(Audio_DSP, "using ffmpeg native opus decoder"); + codec = avcodec_find_decoder(AV_CODEC_ID_OPUS); + } + if (codec) { + if ((avc = avc ? avc : avcodec_alloc_context3(codec))) { + if (is_libopus) { + const std::array mapping_arr{0, 1}; + mappings = mappings ? mappings : mapping_arr.data(); + + // freed by avcodec_context_free() + u8 *edata = reinterpret_cast(av_mallocz(OPUS_HEAD_SIZE + 2 * OPUS_MAX_CHANNELS + AV_INPUT_BUFFER_PADDING_SIZE)); + ASSERT(edata); + edata[9] = u8(channel_count); //channels + edata[10] = u8(0); //opus->pre_skip + edata[16] = u8(0); //gain_db + edata[18] = u8(0); //channel_map + edata[OPUS_HEAD_SIZE + 0] = u8(total_stream_count); + edata[OPUS_HEAD_SIZE + 1] = u8(stereo_stream_count); + if (channel_count >= 1) edata[OPUS_HEAD_SIZE + 2] = mappings[0]; + if (channel_count >= 2) edata[OPUS_HEAD_SIZE + 3] = mappings[1]; + avc->extradata = edata; + avc->extradata_size = OPUS_HEAD_SIZE + 2 * channel_count; + } + + + // FFmpeg hardcodes sample rate + avc->sample_rate = sample_rate; + avc->request_sample_fmt = AV_SAMPLE_FMT_S16; + av_channel_layout_default(&avc->ch_layout, channel_count); + if (avcodec_open2(avc, codec, nullptr) >= 0) { + avpkt = av_packet_alloc(); + frame = av_frame_alloc(); + return ResultSuccess; + } else { + avcodec_free_context(&avc); + } + } + } + return Service::Audio::ResultLibOpusInternalError; + } + + Result Shutdown() { + avcodec_free_context(&avc); + av_frame_free(&frame); + av_packet_free(&avpkt); + return ResultSuccess; + } + + Result ResetDecoder() { + if (avc) { + if (avcodec_is_open(avc)) avcodec_flush_buffers(avc); + return ResultSuccess; + } + return Service::Audio::ResultLibOpusInvalidState; + } + + Result Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size) { + out_sample_count = 0; + if (avc) { + int rem_output_bytes = int(output_data_size); + while (rem_output_bytes > 0) { + int r = avcodec_receive_frame(avc, frame); + if (r == AVERROR(EAGAIN)) { + av_packet_unref(avpkt); + av_new_packet(avpkt, int(input_data_size)); + std::memcpy(avpkt->data, reinterpret_cast(input_data), input_data_size); + r = avcodec_send_packet(avc, avpkt); + ASSERT(r >= 0); + } else if (r == AVERROR_EOF) { + break; + } else if (r >= 0) { + auto const bsize = av_samples_get_buffer_size(nullptr, frame->ch_layout.nb_channels, frame->nb_samples, AV_SAMPLE_FMT_S16, 1); + if (frame->format == AV_SAMPLE_FMT_S16) { + std::memcpy(reinterpret_cast(output_data) + (int(output_data_size) - rem_output_bytes), frame->data[0], size_t(bsize)); + } else { + SwrContext *swr = nullptr; + if (swr_alloc_set_opts2( + &swr, + &avc->ch_layout, + AV_SAMPLE_FMT_S16, + 48000, + &avc->ch_layout, + (enum AVSampleFormat)frame->format, + 48000, + 0, + nullptr + ) >= 0) { + if (swr_init(swr) >= 0) { + AVFrame *s16_frame = av_frame_alloc(); + s16_frame->format = AV_SAMPLE_FMT_S16; + s16_frame->sample_rate = frame->sample_rate; + av_channel_layout_copy(&s16_frame->ch_layout, &frame->ch_layout); + s16_frame->nb_samples = frame->nb_samples; + av_frame_get_buffer(s16_frame, 0); + swr_convert(swr, s16_frame->data, s16_frame->nb_samples, (const uint8_t **)frame->data, frame->nb_samples); + std::memcpy(reinterpret_cast(output_data) + (int(output_data_size) - rem_output_bytes), s16_frame->data[0], size_t(bsize)); + swr_free(&swr); + } + } + } + out_sample_count += frame->nb_samples; + rem_output_bytes -= bsize; + } else { + LOG_ERROR(Audio_DSP, "{}", r); + break; + } + } + ASSERT(rem_output_bytes == 0 && "remaining bytes!"); + return ResultSuccess; + } + return Service::Audio::ResultLibOpusInvalidState; + } + + AVCodecContext* avc = nullptr; + AVPacket* avpkt = nullptr; + AVFrame* frame = nullptr; +}; } // namespace -OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} { - init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); }); +OpusDecoder::OpusDecoder(Core::System& system) { + dsp_thread = std::jthread([this, &system](std::stop_token stop_token) { + Common::SetCurrentThreadName("DSP_OpusDecoder"); + if (Receive(Direction::DSP, stop_token) != Message::Start) { + LOG_ERROR(Service_Audio, "DSP OpusDecoder failed to receive Start message. Opus initialization failed."); + return; + } + Send(Direction::Host, Message::StartOK); + + // Main OpusDecoder thread, responsible for processing the incoming Opus packets. + ::Common::unordered_map decode_objects; + 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] = OpusGenericDecodeObject::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 = 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 >= OpusGenericDecodeObject::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, 1, channel_count, channel_count == 2 ? 1 : 0, nullptr).raw; + } else { + OpusGenericDecodeObject obj{}; + shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, 1, channel_count, channel_count == 2 ? 1 : 0, nullptr).raw; + decode_objects.insert_or_assign(buffer, obj); + } + 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]; + 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; + } + 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; + } + 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 = s32(shared_memory->host_send_data[0]); + auto stereo_stream_count = s32(shared_memory->host_send_data[1]); + + ASSERT(IsValidStreamCounts(total_stream_count, stereo_stream_count)); + + shared_memory->dsp_return_data[0] = OpusGenericDecodeObject::GetWorkBufferSizeMultistream(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(IsValidStreamCounts(total_stream_count, stereo_stream_count)); + ASSERT(sample_rate >= 0); + ASSERT(buffer_size >= OpusGenericDecodeObject::GetWorkBufferSizeMultistream(total_stream_count, stereo_stream_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, total_stream_count, channel_count, stereo_stream_count, mappings).raw; + } else { + OpusGenericDecodeObject obj{}; + shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw; + 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 = 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::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 = 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::DecodeInterleavedForMultiStreamOK); + break; + } + default: + LOG_ERROR(Audio_DSP, "Invalid OpusDecoder command {}", msg); + continue; + } + } + for (auto e : decode_objects) + e.second.Shutdown(); + }); } OpusDecoder::~OpusDecoder() { - if (!running) { - init_thread.request_stop(); - return; + if (dsp_thread.joinable()) { + // Shutdown the thread + auto const stop_token = dsp_thread.get_stop_token(); + Send(Direction::DSP, Message::Shutdown); + auto msg = Receive(Direction::Host, stop_token); + ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", Message::ShutdownOK, msg); + dsp_thread.request_stop(); + dsp_thread.join(); } - - // 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); - main_thread.request_stop(); - main_thread.join(); - running = false; } void OpusDecoder::Send(Direction dir, u32 message) { @@ -64,206 +397,4 @@ u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) { return mailbox.Receive(dir, stop_token); } -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."); - 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(); - } - - if (error_code == OPUS_OK) { - error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size, - input_data, input_data_size); - } - - if (error_code == OPUS_OK) { - if (final_range && decoder_object.GetFinalRange() != final_range) { - error_code = OPUS_INVALID_PACKET; - } - } - - 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(); - } - - if (error_code == OPUS_OK) { - error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size, - input_data, input_data_size); - } - - if (error_code == OPUS_OK) { - if (final_range && decoder_object.GetFinalRange() != final_range) { - error_code = OPUS_INVALID_PACKET; - } - } - - 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::DecodeInterleavedForMultiStreamOK); - } break; - - default: - LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg); - continue; - } - } -} - } // 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..dd87b473d1 100644 --- a/src/audio_core/adsp/apps/opus/opus_decoder.h +++ b/src/audio_core/adsp/apps/opus/opus_decoder.h @@ -6,12 +6,13 @@ #pragma once -#include #include +#include "common/container/unordered_map.h" #include "audio_core/adsp/apps/opus/shared_memory.h" #include "audio_core/adsp/mailbox.h" #include "common/common_types.h" +#include "core/hle/result.h" namespace Core { class System; @@ -48,16 +49,14 @@ enum Message : u32 { DecodeInterleavedForMultiStreamOK = 50, }; -/** - * The AudioRenderer application running on the ADSP. - */ +/// @brief The AudioRenderer application running on the ADSP. class OpusDecoder { public: explicit OpusDecoder(Core::System& system); ~OpusDecoder(); bool IsRunning() const noexcept { - return running; + return dsp_thread.joinable(); } void Send(Direction dir, u32 message); @@ -68,28 +67,12 @@ public: } private: - /** - * 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. - */ - void Main(std::stop_token stop_token); - - /// Core system - Core::System& system; /// Mailbox to communicate messages with the host, drives the main thread Mailbox mailbox; - /// Init thread - std::jthread init_thread{}; - /// Main thread - std::jthread main_thread{}; - /// The current state - bool running{}; /// 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{}; + std::jthread dsp_thread{}; }; } // 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 deleted file mode 100644 index 6fecd63309..0000000000 --- a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp +++ /dev/null @@ -1,113 +0,0 @@ -// 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 - -#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h" -#include "common/assert.h" - -namespace AudioCore::ADSP::OpusDecoder { - -namespace { -constexpr u32 OpusStreamCountMax = 255; - -bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) { - return total_stream_count > 0 && total_stream_count <= OpusStreamCountMax && - static_cast(stereo_stream_count) >= 0 && - stereo_stream_count <= 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); - } - 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; -} - -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; -} - -s32 OpusMultiStreamDecodeObject::Shutdown() { - if (!state_valid) { - return OPUS_INVALID_STATE; - } - - if (initialized) { - magic = 0x0; - initialized = false; - state_valid = false; - self = nullptr; - final_range = 0; - decoder = nullptr; - } - return OPUS_OK; -} - -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); - out_sample_count = 0; - - if (!state_valid) { - return OPUS_INVALID_STATE; - } - - 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); -} - -} // 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 deleted file mode 100644 index 93558ded5d..0000000000 --- a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include "common/common_types.h" - -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; -}; -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/adsp/apps/opus/shared_memory.h b/src/audio_core/adsp/apps/opus/shared_memory.h index c696731ed8..45079c6292 100644 --- a/src/audio_core/adsp/apps/opus/shared_memory.h +++ b/src/audio_core/adsp/apps/opus/shared_memory.h @@ -1,9 +1,11 @@ +// 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 "common/common_funcs.h" #include "common/common_types.h" namespace AudioCore::ADSP::OpusDecoder { diff --git a/src/audio_core/opus/hardware_opus.cpp b/src/audio_core/opus/hardware_opus.cpp index 04591c10b6..c07a358168 100644 --- a/src/audio_core/opus/hardware_opus.cpp +++ b/src/audio_core/opus/hardware_opus.cpp @@ -10,39 +10,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); } @@ -112,7 +91,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, @@ -140,7 +119,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) { @@ -154,7 +133,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) { @@ -169,7 +148,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, @@ -193,12 +172,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, @@ -207,29 +186,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) { @@ -240,8 +217,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(); @@ -255,8 +231,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 b6c7fbe6c8..26c96e00c0 100644 --- a/src/audio_core/opus/hardware_opus.h +++ b/src/audio_core/opus/hardware_opus.h @@ -8,8 +8,6 @@ #include #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" diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 508ace3501..e72fd791b9 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1199,7 +1199,6 @@ else() endif() endif() -target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS}) target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz) if (BOOST_NO_HEADERS) diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 64d14ab2a6..06b77a2a8f 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -414,9 +414,7 @@ endif() if (YUZU_USE_EXTERNAL_FFMPEG) add_dependencies(video_core ffmpeg-build) endif() - target_include_directories(video_core PUBLIC ${FFmpeg_INCLUDE_DIR}) - target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES}) target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})