diff --git a/.patch/opus/0001-disable-clang-runtime-neon.patch b/.patch/opus/0001-disable-clang-runtime-neon.patch new file mode 100644 index 0000000000..27a905c366 --- /dev/null +++ b/.patch/opus/0001-disable-clang-runtime-neon.patch @@ -0,0 +1,28 @@ +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 new file mode 100644 index 0000000000..833a3e4178 --- /dev/null +++ b/.patch/opus/0002-no-install.patch @@ -0,0 +1,153 @@ +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 f5c360b294..266331b0ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -464,6 +464,21 @@ 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 new file mode 100644 index 0000000000..257642682e --- /dev/null +++ b/CMakeModules/find/FindOpus.cmake @@ -0,0 +1,22 @@ +# 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 419c8d2d69..8b2099f40a 100644 --- a/cpmfile.json +++ b/cpmfile.json @@ -82,7 +82,7 @@ "name": "ffmpeg", "package": "FFmpeg", "repo": "crueter-ci/FFmpeg", - "version": "9.0.1-1788303113-bf1b838f2a" + "version": "9.0.1-1788120736-bf1b838f2a" }, "fmt": { "hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2", @@ -210,6 +210,21 @@ "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 9a781ebdcd..2b73ecb4ac 100644 --- a/docs/Deps.md +++ b/docs/Deps.md @@ -60,6 +60,7 @@ 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: @@ -120,7 +121,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-video/ffmpeg \ + media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \ media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \ net-libs/enet \ sys-libs/zlib \ @@ -152,7 +153,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 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 opus 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. @@ -165,7 +166,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 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 libopus-dev libasound2t64 vulkan-utility-libraries-dev ``` * Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required. @@ -212,7 +213,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 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 opus-dev jq patch ``` @@ -260,7 +261,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 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 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 ``` If using FreeBSD 12 or prior, use `devel/pkg-config` instead. @@ -274,7 +275,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 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 libopus 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). @@ -305,7 +306,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 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 opus sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt ``` [Caveats](./Caveats.md#openindiana). @@ -329,7 +330,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 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 opus libusb openssl SDL3" # Either x86_64 or clang-aarch64 (Windows on ARM) packages="$BASE" for pkg in $MINGW; do @@ -355,7 +356,7 @@ pacman -Syuu --needed --noconfirm $packages HaikuOS ```sh -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 +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 ``` [Caveats](./Caveats.md#haikuos). diff --git a/shell.nix b/shell.nix index e4d57d7e5e..786b94ccb2 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 vulkan-headers vulkan-utility-libraries + enet libopus 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 226edfa00c..32b72fe38f 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,6 +79,13 @@ 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 e712ec4338..730f6bbade 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -1784,6 +1784,51 @@ 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 71a46a7b1e..b244d657a9 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -15,6 +15,10 @@ 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 @@ -222,14 +226,8 @@ else() $<$:-Wno-sign-conversion>) endif() -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) +target_include_directories(audio_core PRIVATE ${OPUS_INCLUDE_DIRS}) +target_link_libraries(audio_core PUBLIC common core Opus::opus) 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 new file mode 100644 index 0000000000..a47a7f8527 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp @@ -0,0 +1,110 @@ +// 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_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 new file mode 100644 index 0000000000..feee9587ce --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_decode_object.h @@ -0,0 +1,41 @@ +// 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" + +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 0be0f774b1..8a009c04c7 100644 --- a/src/audio_core/adsp/apps/opus/opus_decoder.cpp +++ b/src/audio_core/adsp/apps/opus/opus_decoder.cpp @@ -5,396 +5,265 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include -extern "C" { -#include -#include -#include -#include -#include -#include -#include -#include -} - +#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/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 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; +constexpr size_t OpusStreamCountMax = 255; bool IsValidChannelCount(u32 channel_count) { - return channel_count >= 1 || channel_count <= OPUS_MAX_CHANNELS; + return channel_count == 1 || channel_count == 2; } -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 IsValidMultiStreamChannelCount(u32 channel_count) { + return channel_count <= OpusStreamCountMax; } -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; - } +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; +} +} // namespace - static u32 GetWorkBufferSize(u32 channel_count) { - if (channel_count == 1 || channel_count == 2) - return 48 + 16 * channel_count; - return 0; - } +OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} { + init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); }); +} - /// 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; - } +OpusDecoder::~OpusDecoder() { + if (!running) { + init_thread.request_stop(); + return; + } + // 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; +} - // 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; - } +void OpusDecoder::Send(Direction dir, u32 message) { + mailbox.Send(dir, std::move(message)); +} - Result Shutdown() { - avcodec_free_context(&avc); - av_frame_free(&frame); - av_packet_free(&avpkt); - return ResultSuccess; - } +u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) { + return mailbox.Receive(dir, stop_token); +} - Result ResetDecoder() { - if (avc) { - if (avcodec_is_open(avc)) avcodec_flush_buffers(avc); - return ResultSuccess; - } - return Service::Audio::ResultLibOpusInvalidState; - } +void OpusDecoder::Init(std::stop_token stop_token) { + Common::SetCurrentThreadName("DSP_OpusDecoder_Init"); - 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; + 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); +} - AVCodecContext* avc = nullptr; - AVPacket* avpkt = nullptr; - AVFrame* frame = nullptr; -}; -} // namespace +void OpusDecoder::Main(std::stop_token stop_token) { + Common::SetCurrentThreadName("DSP_OpusDecoder_Main"); -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."); + while (!stop_token.stop_requested()) { + auto msg = Receive(Direction::DSP, stop_token); + switch (msg) { + case Shutdown: + Send(Direction::Host, Message::ShutdownOK); 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 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(); } - 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; + + if (error_code == OPUS_OK) { + error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size, + input_data, input_data_size); } - 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; + + if (error_code == OPUS_OK) { + if (final_range && decoder_object.GetFinalRange() != final_range) { + error_code = OPUS_INVALID_PACKET; } - 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; + 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 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; + + if (error_code == OPUS_OK) { + error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size, + input_data, input_data_size); } - 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; + + if (error_code == OPUS_OK) { + if (final_range && decoder_object.GetFinalRange() != final_range) { + error_code = OPUS_INVALID_PACKET; } - 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 (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(); - } -} + 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(); -void OpusDecoder::Send(Direction dir, u32 message) { - mailbox.Send(dir, std::move(message)); -} + Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK); + } break; -u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) { - return mailbox.Receive(dir, stop_token); + 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 dd87b473d1..5b3f22bf4c 100644 --- a/src/audio_core/adsp/apps/opus/opus_decoder.h +++ b/src/audio_core/adsp/apps/opus/opus_decoder.h @@ -6,13 +6,12 @@ #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; @@ -49,14 +48,16 @@ enum Message : u32 { DecodeInterleavedForMultiStreamOK = 50, }; -/// @brief The AudioRenderer application running on the ADSP. +/** + * The AudioRenderer application running on the ADSP. + */ class OpusDecoder { public: explicit OpusDecoder(Core::System& system); ~OpusDecoder(); bool IsRunning() const noexcept { - return dsp_thread.joinable(); + return running; } void Send(Direction dir, u32 message); @@ -67,12 +68,28 @@ 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 new file mode 100644 index 0000000000..6fecd63309 --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp @@ -0,0 +1,113 @@ +// 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 new file mode 100644 index 0000000000..baad58385e --- /dev/null +++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h @@ -0,0 +1,42 @@ +// 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" + +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 deleted file mode 100644 index bacfb30338..0000000000 --- a/src/audio_core/adsp/apps/opus/opus_types.h +++ /dev/null @@ -1,32 +0,0 @@ -// 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 45079c6292..b15a1837cc 100644 --- a/src/audio_core/adsp/apps/opus/shared_memory.h +++ b/src/audio_core/adsp/apps/opus/shared_memory.h @@ -6,6 +6,7 @@ #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 c07a358168..04591c10b6 100644 --- a/src/audio_core/opus/hardware_opus.cpp +++ b/src/audio_core/opus/hardware_opus.cpp @@ -10,18 +10,39 @@ #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); } @@ -91,7 +112,7 @@ Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count, R_THROW(ResultInvalidOpusDSPReturnCode); } - R_RETURN(Result(u32(shared_memory.dsp_return_data[0]))); + R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); } Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count, @@ -119,7 +140,7 @@ Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 chan R_THROW(ResultInvalidOpusDSPReturnCode); } - R_RETURN(Result(u32(shared_memory.dsp_return_data[0]))); + R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); } Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) { @@ -133,7 +154,7 @@ Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) { "Expected Opus shutdown code {}, got {}", ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg); - R_RETURN(Result(u32(shared_memory.dsp_return_data[0]))); + R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); } Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) { @@ -148,7 +169,7 @@ Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_si "Expected Opus shutdown code {}, got {}", ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg); - R_RETURN(Result(u32(shared_memory.dsp_return_data[0]))); + R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); } Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data, @@ -172,12 +193,12 @@ Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data, R_THROW(ResultInvalidOpusDSPReturnCode); } - 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]); + 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]); out_time_taken = 1000 * shared_memory.dsp_return_data[2]; } - R_RETURN(Result(u32(error_code))); + R_RETURN(ResultCodeFromLibOpusErrorCode(error_code)); } Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data, @@ -186,27 +207,29 @@ 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 const error_code = shared_memory.dsp_return_data[0]; - if (error_code == ResultSuccess.raw) { + 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]); out_time_taken = 1000 * shared_memory.dsp_return_data[2]; } - R_RETURN(Result(u32(shared_memory.dsp_return_data[0]))); + R_RETURN(ResultCodeFromLibOpusErrorCode(error_code)); } Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) { @@ -217,7 +240,8 @@ 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(); @@ -231,7 +255,8 @@ 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 26c96e00c0..b6c7fbe6c8 100644 --- a/src/audio_core/opus/hardware_opus.h +++ b/src/audio_core/opus/hardware_opus.h @@ -8,6 +8,8 @@ #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 e72fd791b9..508ace3501 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1199,6 +1199,7 @@ 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 06b77a2a8f..64d14ab2a6 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -414,7 +414,9 @@ 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})