aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlizzie <lizzie@eden-emu.dev>2026-09-14 14:34:24 +0200
committercrueter <crueter@eden-emu.dev>2026-09-14 14:34:24 +0200
commit2b4184dd2bb33da081702263d59c3eadb299e673 (patch)
treed576406f4584d9334e25f31a842302472f953046
parentd9159fefdd9e8850e550dd47cc59d7da999a2380 (diff)
Revert "[audio] Replace libopus with FFmpeg (#4169)" (#4419)
This reverts commit ff8368c6066faf3962e2f77af7da4954e2ff6a43. - [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions). - [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md) - [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability. ------------------- Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4419 Reviewed-by: Maufeat <sahyno1996@gmail.com> Reviewed-by: MaranBr <maranbr@eden-emu.dev>
-rw-r--r--.patch/opus/0001-disable-clang-runtime-neon.patch28
-rw-r--r--.patch/opus/0002-no-install.patch153
-rw-r--r--CMakeLists.txt15
-rw-r--r--CMakeModules/find/FindOpus.cmake22
-rw-r--r--cpmfile.json17
-rw-r--r--docs/Deps.md19
-rwxr-xr-xshell.nix2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LicensesFragment.kt7
-rw-r--r--src/android/app/src/main/res/values/strings.xml45
-rw-r--r--src/audio_core/CMakeLists.txt14
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decode_object.cpp110
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decode_object.h41
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decoder.cpp569
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decoder.h27
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp113
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h42
-rw-r--r--src/audio_core/adsp/apps/opus/opus_types.h32
-rw-r--r--src/audio_core/adsp/apps/opus/shared_memory.h1
-rw-r--r--src/audio_core/opus/hardware_opus.cpp71
-rw-r--r--src/audio_core/opus/hardware_opus.h2
-rw-r--r--src/core/CMakeLists.txt1
-rw-r--r--src/video_core/CMakeLists.txt2
22 files changed, 904 insertions, 429 deletions
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 <crueter@eden-emu.dev>
+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 <crueter@eden-emu.dev>
+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 <crueter@eden-emu.dev>
+---
+ 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
+ $<$<COMPILE_LANGUAGE:C,CXX>:-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:
<summary>Arch Linux</summary>
```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
<summary>Ubuntu, Debian, Mint Linux</summary>
```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
```
</details>
@@ -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
<summary>OpenIndiana</summary>
```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
<summary>HaikuOS</summary>
```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
@@ -80,6 +80,13 @@ class LicensesFragment : Fragment() {
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,
R.string.license_sirit_link,
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
@@ -1785,6 +1785,51 @@ 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.
</string>
+ <string name="license_opus" translatable="false">Opus</string>
+ <string name="license_opus_description" translatable="false">Modern audio compression for the internet</string>
+ <string name="license_opus_link" translatable="false">https://github.com/xiph/opus</string>
+ <string name="license_opus_copyright" translatable="false">Copyright 2001–2011 Xiph.Org, Skype Limited, Octasic, Jean-Marc Valin, Timothy B. Terriberry, CSIRO, Gregory Maxwell, Mark Borgerding, Erik de Castro Lopo</string>
+ <string name="license_opus_text" translatable="false">
+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/
+ </string>
<string name="license_sirit" translatable="false">Sirit</string>
<string name="license_sirit_description" translatable="false">A runtime SPIR-V assembler</string>
<string name="license_sirit_link" translatable="false">https://github.com/ReinUsesLisp/sirit</string>
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()
$<$<COMPILE_LANGUAGE:C,CXX>:-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<u32>(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count);
+}
+
+OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) {
+ auto* new_decoder = reinterpret_cast<OpusDecodeObject*>(buffer);
+ auto* comparison = reinterpret_cast<OpusDecodeObject*>(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<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
+ reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(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 <opus.h>
+
+#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<OpusDecodeObject>);
+
+} // 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 <array>
+#include <chrono>
-extern "C" {
-#include <libswresample/swresample.h>
-#include <libavcodec/avcodec.h>
-#include <libavcodec/codec.h>
-#include <libavcodec/packet.h>
-#include <libavutil/channel_layout.h>
-#include <libavutil/frame.h>
-#include <libavutil/opt.h>
-#include <libavutil/samplefmt.h>
-}
-
+#include "audio_core/adsp/apps/opus/opus_decode_object.h"
+#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
#include "audio_core/adsp/apps/opus/shared_memory.h"
#include "audio_core/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<u8, 2> mapping_arr{0, 1};
- mappings = mappings ? mappings : mapping_arr.data();
-
- // freed by avcodec_context_free()
- u8 *edata = reinterpret_cast<u8*>(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<const u8*>(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<s16*>(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<s16*>(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<u64, OpusGenericDecodeObject> 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<s32>(shared_memory->host_send_data[0]);
+
+ ASSERT(IsValidChannelCount(channel_count));
+
+ shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count);
+ Send(Direction::Host, Message::GetWorkBufferSizeOK);
+ } break;
+
+ case InitializeDecodeObject: {
+ auto buffer = shared_memory->host_send_data[0];
+ auto buffer_size = shared_memory->host_send_data[1];
+ auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
+ auto channel_count = static_cast<s32>(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<u32>(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<u32>(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<s32>(shared_memory->host_send_data[0]);
+ auto stereo_stream_count = static_cast<s32>(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<s32>(shared_memory->host_send_data[2]);
+ auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
+ auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[4]);
+ auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[5]);
+ // Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
+ // mappings, but [6] is never set, and there is not enough room in the argument data for
+ // more than 40 channels, when 255 are possible.
+ // It also means the mapping values are undefined, though likely always 0,
+ // and the mappings given by the game are ignored. The mappings are copied to this
+ // dedicated buffer host side, so let's do as intended.
+ auto mappings = shared_memory->channel_mapping.data();
+
+ ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
+ ASSERT(sample_rate >= 0);
+ ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize(
+ total_stream_count, stereo_stream_count));
+
+ 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<u32>(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<u32>(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 <memory>
#include <thread>
-#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<s32>(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<u32>(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<OpusMultiStreamDecodeObject*>(buffer);
+ auto* comparison = reinterpret_cast<OpusMultiStreamDecodeObject*>(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<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
+ reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(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 <opus_multistream.h>
+
+#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<OpusMultiStreamDecodeObject>);
+
+} // 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<s32>(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<s32>(shared_memory.dsp_return_data[0])};
+ if (error_code == OPUS_OK) {
+ out_sample_count = static_cast<u32>(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<s32>(shared_memory.dsp_return_data[0])};
+ if (error_code == OPUS_OK) {
out_sample_count = static_cast<u32>(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 <array>
#include <mutex>
+#include <opus.h>
+
#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})