mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-12 14:48:49 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ff8368c606 |
@@ -1,28 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -464,21 +464,6 @@ if (NOT YUZU_STATIC_ROOM)
|
|||||||
if (ZLIB_ADDED)
|
if (ZLIB_ADDED)
|
||||||
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
||||||
endif()
|
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()
|
endif()
|
||||||
|
|
||||||
if(NOT TARGET Boost::headers)
|
if(NOT TARGET Boost::headers)
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
find_package(PkgConfig QUIET)
|
|
||||||
pkg_search_module(OPUS QUIET IMPORTED_TARGET opus)
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
find_package_handle_standard_args(Opus
|
|
||||||
REQUIRED_VARS OPUS_LINK_LIBRARIES
|
|
||||||
VERSION_VAR OPUS_VERSION
|
|
||||||
)
|
|
||||||
|
|
||||||
if (MSYS2)
|
|
||||||
FixMsysPath(PkgConfig::OPUS)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (Opus_FOUND AND NOT TARGET Opus::opus)
|
|
||||||
add_library(Opus::opus ALIAS PkgConfig::OPUS)
|
|
||||||
endif()
|
|
||||||
+1
-16
@@ -82,7 +82,7 @@
|
|||||||
"name": "ffmpeg",
|
"name": "ffmpeg",
|
||||||
"package": "FFmpeg",
|
"package": "FFmpeg",
|
||||||
"repo": "crueter-ci/FFmpeg",
|
"repo": "crueter-ci/FFmpeg",
|
||||||
"version": "9.0.1-1788120736-bf1b838f2a"
|
"version": "9.0.1-1788303113-bf1b838f2a"
|
||||||
},
|
},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2",
|
"hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2",
|
||||||
@@ -210,21 +210,6 @@
|
|||||||
"repo": "jimmy-park/openssl-cmake",
|
"repo": "jimmy-park/openssl-cmake",
|
||||||
"version": "3.6.2"
|
"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": {
|
"quazip": {
|
||||||
"hash": "609c240c7f029ac26a37d8fbab51bc16284e05e128b78b9b9c0e95d083538c36047a67d682759ac990e4adb0eeb90f04f1ea7fe2253bbda7e7e3bcce32e53dd8",
|
"hash": "609c240c7f029ac26a37d8fbab51bc16284e05e128b78b9b9c0e95d083538c36047a67d682759ac990e4adb0eeb90f04f1ea7fe2253bbda7e7e3bcce32e53dd8",
|
||||||
"min_version": "1.3",
|
"min_version": "1.3",
|
||||||
|
|||||||
+9
-10
@@ -60,7 +60,6 @@ All other dependencies will be downloaded and built by [CPM](https://github.com/
|
|||||||
* [ZLIB](https://www.zlib.net/) 1.2+
|
* [ZLIB](https://www.zlib.net/) 1.2+
|
||||||
* [zstd](https://facebook.github.io/zstd/) 1.5+
|
* [zstd](https://facebook.github.io/zstd/) 1.5+
|
||||||
* [enet](http://enet.bespin.org/) 1.3+
|
* [enet](http://enet.bespin.org/) 1.3+
|
||||||
* [Opus](https://opus-codec.org/) 1.3+
|
|
||||||
|
|
||||||
Vulkan 1.3.274+ is also needed:
|
Vulkan 1.3.274+ is also needed:
|
||||||
|
|
||||||
@@ -121,7 +120,7 @@ sudo emerge -a \
|
|||||||
dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \
|
dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \
|
||||||
dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \
|
dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \
|
||||||
dev-util/vulkan-utility-libraries dev-util/glslang \
|
dev-util/vulkan-utility-libraries dev-util/glslang \
|
||||||
media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \
|
media-gfx/renderdoc media-libs/libva media-video/ffmpeg \
|
||||||
media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \
|
media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \
|
||||||
net-libs/enet \
|
net-libs/enet \
|
||||||
sys-libs/zlib \
|
sys-libs/zlib \
|
||||||
@@ -153,7 +152,7 @@ Required USE flags:
|
|||||||
<summary>Arch Linux</summary>
|
<summary>Arch Linux</summary>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl opus qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
|
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
|
||||||
```
|
```
|
||||||
|
|
||||||
* Building with QT Web Engine requires `qt6-webengine` as well.
|
* Building with QT Web Engine requires `qt6-webengine` as well.
|
||||||
@@ -166,7 +165,7 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl
|
|||||||
<summary>Ubuntu, Debian, Mint Linux</summary>
|
<summary>Ubuntu, Debian, Mint Linux</summary>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev
|
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libasound2t64 vulkan-utility-libraries-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
* Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required.
|
* Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required.
|
||||||
@@ -213,7 +212,7 @@ First, enable the community repository; [see here](https://wiki.alpinelinux.org/
|
|||||||
# Enable the community repository
|
# Enable the community repository
|
||||||
setup-apkrepos -c
|
setup-apkrepos -c
|
||||||
# Install
|
# Install
|
||||||
apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch
|
apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev jq patch
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
@@ -261,7 +260,7 @@ brew install molten-vk
|
|||||||
|
|
||||||
As root run:
|
As root run:
|
||||||
```sh
|
```sh
|
||||||
pkg install devel/cmake devel/sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base x11-toolkits/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib graphics/vulkan-utility-libraries graphics/vulkan-headers graphics/spirv-headers quazip-qt6
|
pkg install devel/cmake devel/sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base x11-toolkits/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib graphics/vulkan-utility-libraries graphics/vulkan-headers graphics/spirv-headers quazip-qt6
|
||||||
```
|
```
|
||||||
|
|
||||||
If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
||||||
@@ -275,7 +274,7 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
|||||||
For NetBSD +10.1:
|
For NetBSD +10.1:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6-qtbase qt6-qtcharts qt6-qtmultimedia qt6-qttools cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen
|
pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq qt6-qtbase qt6-qtcharts qt6-qtmultimedia qt6-qttools cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen
|
||||||
```
|
```
|
||||||
|
|
||||||
[Caveats](./Caveats.md#netbsd).
|
[Caveats](./Caveats.md#netbsd).
|
||||||
@@ -306,7 +305,7 @@ pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmak
|
|||||||
<summary>OpenIndiana</summary>
|
<summary>OpenIndiana</summary>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
|
sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
|
||||||
```
|
```
|
||||||
|
|
||||||
[Caveats](./Caveats.md#openindiana).
|
[Caveats](./Caveats.md#openindiana).
|
||||||
@@ -330,7 +329,7 @@ sudo pkgin install git cmake autoconf build-essential libusb-1 nasm gcc13
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
BASE="git make autoconf libtool automake-wrapper jq patch"
|
BASE="git make autoconf libtool automake-wrapper jq patch"
|
||||||
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb openssl SDL3"
|
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet libusb openssl SDL3"
|
||||||
# Either x86_64 or clang-aarch64 (Windows on ARM)
|
# Either x86_64 or clang-aarch64 (Windows on ARM)
|
||||||
packages="$BASE"
|
packages="$BASE"
|
||||||
for pkg in $MINGW; do
|
for pkg in $MINGW; do
|
||||||
@@ -356,7 +355,7 @@ pacman -Syuu --needed --noconfirm $packages
|
|||||||
<summary>HaikuOS</summary>
|
<summary>HaikuOS</summary>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
|
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
|
||||||
```
|
```
|
||||||
|
|
||||||
[Caveats](./Caveats.md#haikuos).
|
[Caveats](./Caveats.md#haikuos).
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ pkgs.mkShellNoCC {
|
|||||||
git cmake clang gnumake patch jq pkg-config
|
git cmake clang gnumake patch jq pkg-config
|
||||||
# libraries
|
# libraries
|
||||||
openssl boost fmt nlohmann_json lz4 zlib zstd
|
openssl boost fmt nlohmann_json lz4 zlib zstd
|
||||||
enet libopus vulkan-headers vulkan-utility-libraries
|
enet vulkan-headers vulkan-utility-libraries
|
||||||
spirv-tools spirv-headers vulkan-loader unzip
|
spirv-tools spirv-headers vulkan-loader unzip
|
||||||
glslang python3 httplib cpp-jwt ffmpeg-headless
|
glslang python3 httplib cpp-jwt ffmpeg-headless
|
||||||
libusb1 cubeb
|
libusb1 cubeb
|
||||||
|
|||||||
@@ -79,13 +79,6 @@ class LicensesFragment : Fragment() {
|
|||||||
R.string.license_ffmpeg_copyright,
|
R.string.license_ffmpeg_copyright,
|
||||||
R.string.license_ffmpeg_text
|
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(
|
License(
|
||||||
R.string.license_sirit,
|
R.string.license_sirit,
|
||||||
R.string.license_sirit_description,
|
R.string.license_sirit_description,
|
||||||
|
|||||||
@@ -1783,51 +1783,6 @@ RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
|||||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
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
|
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
DAMAGES.
|
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>
|
||||||
<string name="license_sirit" translatable="false">Sirit</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_description" translatable="false">A runtime SPIR-V assembler</string>
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ add_library(audio_core STATIC
|
|||||||
adsp/apps/audio_renderer/command_list_processor.h
|
adsp/apps/audio_renderer/command_list_processor.h
|
||||||
adsp/apps/opus/opus_decoder.cpp
|
adsp/apps/opus/opus_decoder.cpp
|
||||||
adsp/apps/opus/opus_decoder.h
|
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
|
adsp/apps/opus/shared_memory.h
|
||||||
audio_core.cpp
|
audio_core.cpp
|
||||||
audio_core.h
|
audio_core.h
|
||||||
@@ -226,8 +222,14 @@ else()
|
|||||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
|
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(audio_core PRIVATE ${OPUS_INCLUDE_DIRS})
|
if (YUZU_USE_EXTERNAL_FFMPEG)
|
||||||
target_link_libraries(audio_core PUBLIC common core Opus::opus)
|
add_dependencies(audio_core ffmpeg-build)
|
||||||
|
endif()
|
||||||
|
target_include_directories(audio_core PUBLIC ${FFmpeg_INCLUDE_DIR})
|
||||||
|
target_link_libraries(audio_core PRIVATE ${FFmpeg_LIBRARIES})
|
||||||
|
target_link_options(audio_core PRIVATE ${FFmpeg_LDFLAGS})
|
||||||
|
|
||||||
|
target_link_libraries(audio_core PUBLIC common core)
|
||||||
|
|
||||||
if (ENABLE_CUBEB)
|
if (ENABLE_CUBEB)
|
||||||
target_sources(audio_core PRIVATE
|
target_sources(audio_core PRIVATE
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
|
|
||||||
#include "common/assert.h"
|
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
|
||||||
namespace {
|
|
||||||
bool IsValidChannelCount(u32 channel_count) {
|
|
||||||
return channel_count == 1 || channel_count == 2;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
u32 OpusDecodeObject::GetWorkBufferSize(u32 channel_count) {
|
|
||||||
if (!IsValidChannelCount(channel_count)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return static_cast<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
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -5,55 +5,388 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
|
extern "C" {
|
||||||
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
|
#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/shared_memory.h"
|
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/audio_core.h"
|
||||||
#include "audio_core/common/common.h"
|
|
||||||
#include "common/logging.h"
|
#include "common/logging.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
|
#include "core/hle/service/audio/errors.h"
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
namespace AudioCore::ADSP::OpusDecoder {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr size_t OpusStreamCountMax = 255;
|
constexpr u32 OPUS_STREAM_COUNT_MAX = 255;
|
||||||
|
// https://git.ffmpeg.org/gitweb/ffmpeg.git/blob_plain/HEAD:/libavcodec/libopusdec.c
|
||||||
|
constexpr u32 OPUS_HEAD_SIZE = 19;
|
||||||
|
constexpr u32 OPUS_MAX_CHANNELS = 2;
|
||||||
|
|
||||||
bool IsValidChannelCount(u32 channel_count) {
|
bool IsValidChannelCount(u32 channel_count) {
|
||||||
return channel_count == 1 || channel_count == 2;
|
return channel_count >= 1 || channel_count <= OPUS_MAX_CHANNELS;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsValidMultiStreamChannelCount(u32 channel_count) {
|
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
|
||||||
return channel_count <= OpusStreamCountMax;
|
return total_stream_count > 0 && total_stream_count <= OPUS_STREAM_COUNT_MAX
|
||||||
|
&& s32(stereo_stream_count) >= 0 && stereo_stream_count <= total_stream_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 stereo_stream_count) {
|
class OpusGenericDecodeObject {
|
||||||
return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 &&
|
public:
|
||||||
stereo_stream_count >= 0 && stereo_stream_count <= total_stream_count;
|
static u32 GetWorkBufferSizeMultistream(u32 total_stream_count, u32 stereo_stream_count) {
|
||||||
}
|
if (IsValidStreamCounts(total_stream_count, stereo_stream_count))
|
||||||
|
return 48 + 2556 * (total_stream_count * stereo_stream_count);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 GetWorkBufferSize(u32 channel_count) {
|
||||||
|
if (channel_count == 1 || channel_count == 2)
|
||||||
|
return 48 + 16 * channel_count;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// idempotency of initialize is guaranteed
|
||||||
|
Result InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, u32 stereo_stream_count, u8 const* mappings) {
|
||||||
|
// prefer libopus, ffmpeg docs say to use libopus **if** available
|
||||||
|
// However, native opus can also work with swrescale:
|
||||||
|
// it uses planarfloat, we can resample to s16
|
||||||
|
AVCodec const* codec = avcodec_find_decoder_by_name("libopus");
|
||||||
|
bool is_libopus = codec != nullptr;
|
||||||
|
if (!codec) {
|
||||||
|
LOG_WARNING(Audio_DSP, "using ffmpeg native opus decoder");
|
||||||
|
codec = avcodec_find_decoder(AV_CODEC_ID_OPUS);
|
||||||
|
}
|
||||||
|
if (codec) {
|
||||||
|
if ((avc = avc ? avc : avcodec_alloc_context3(codec))) {
|
||||||
|
if (is_libopus) {
|
||||||
|
const std::array<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FFmpeg hardcodes sample rate
|
||||||
|
avc->sample_rate = sample_rate;
|
||||||
|
avc->request_sample_fmt = AV_SAMPLE_FMT_S16;
|
||||||
|
av_channel_layout_default(&avc->ch_layout, channel_count);
|
||||||
|
if (avcodec_open2(avc, codec, nullptr) >= 0) {
|
||||||
|
avpkt = av_packet_alloc();
|
||||||
|
frame = av_frame_alloc();
|
||||||
|
return ResultSuccess;
|
||||||
|
} else {
|
||||||
|
avcodec_free_context(&avc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Service::Audio::ResultLibOpusInternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Shutdown() {
|
||||||
|
avcodec_free_context(&avc);
|
||||||
|
av_frame_free(&frame);
|
||||||
|
av_packet_free(&avpkt);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ResetDecoder() {
|
||||||
|
if (avc) {
|
||||||
|
if (avcodec_is_open(avc)) avcodec_flush_buffers(avc);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
return Service::Audio::ResultLibOpusInvalidState;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size) {
|
||||||
|
out_sample_count = 0;
|
||||||
|
if (avc) {
|
||||||
|
int rem_output_bytes = int(output_data_size);
|
||||||
|
while (rem_output_bytes > 0) {
|
||||||
|
int r = avcodec_receive_frame(avc, frame);
|
||||||
|
if (r == AVERROR(EAGAIN)) {
|
||||||
|
av_packet_unref(avpkt);
|
||||||
|
av_new_packet(avpkt, int(input_data_size));
|
||||||
|
std::memcpy(avpkt->data, reinterpret_cast<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVCodecContext* avc = nullptr;
|
||||||
|
AVPacket* avpkt = nullptr;
|
||||||
|
AVFrame* frame = nullptr;
|
||||||
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
|
OpusDecoder::OpusDecoder(Core::System& system) {
|
||||||
init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); });
|
dsp_thread = std::jthread([this, &system](std::stop_token stop_token) {
|
||||||
|
Common::SetCurrentThreadName("DSP_OpusDecoder");
|
||||||
|
if (Receive(Direction::DSP, stop_token) != Message::Start) {
|
||||||
|
LOG_ERROR(Service_Audio, "DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::StartOK);
|
||||||
|
|
||||||
|
// Main OpusDecoder thread, responsible for processing the incoming Opus packets.
|
||||||
|
::Common::unordered_map<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 ShutdownDecodeObject: {
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
//[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
shared_memory->dsp_return_data[0] = it->second.Shutdown().raw;
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||||
|
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DecodeInterleaved: {
|
||||||
|
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||||
|
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
auto input_data = shared_memory->host_send_data[1];
|
||||||
|
auto input_data_size = shared_memory->host_send_data[2];
|
||||||
|
auto output_data = shared_memory->host_send_data[3];
|
||||||
|
auto output_data_size = shared_memory->host_send_data[4];
|
||||||
|
//auto final_range = static_cast<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;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::DecodeInterleavedOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MapMemory: {
|
||||||
|
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||||
|
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
Send(Direction::Host, Message::MapMemoryOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UnmapMemory: {
|
||||||
|
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||||
|
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
Send(Direction::Host, Message::UnmapMemoryOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GetWorkBufferSizeForMultiStream: {
|
||||||
|
auto total_stream_count = s32(shared_memory->host_send_data[0]);
|
||||||
|
auto stereo_stream_count = s32(shared_memory->host_send_data[1]);
|
||||||
|
|
||||||
|
ASSERT(IsValidStreamCounts(total_stream_count, stereo_stream_count));
|
||||||
|
|
||||||
|
shared_memory->dsp_return_data[0] = OpusGenericDecodeObject::GetWorkBufferSizeMultistream(total_stream_count, stereo_stream_count);
|
||||||
|
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case InitializeMultiStreamDecodeObject: {
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
auto sample_rate = s32(shared_memory->host_send_data[2]);
|
||||||
|
auto channel_count = s32(shared_memory->host_send_data[3]);
|
||||||
|
auto total_stream_count = s32(shared_memory->host_send_data[4]);
|
||||||
|
auto stereo_stream_count = s32(shared_memory->host_send_data[5]);
|
||||||
|
// Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
|
||||||
|
// mappings, but [6] is never set, and there is not enough room in the argument data for
|
||||||
|
// more than 40 channels, when 255 are possible.
|
||||||
|
// It also means the mapping values are undefined, though likely always 0,
|
||||||
|
// and the mappings given by the game are ignored. The mappings are copied to this
|
||||||
|
// dedicated buffer host side, so let's do as intended.
|
||||||
|
auto mappings = shared_memory->channel_mapping.data();
|
||||||
|
|
||||||
|
ASSERT(IsValidStreamCounts(total_stream_count, stereo_stream_count));
|
||||||
|
ASSERT(sample_rate >= 0);
|
||||||
|
ASSERT(buffer_size >= OpusGenericDecodeObject::GetWorkBufferSizeMultistream(total_stream_count, stereo_stream_count));
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
it->second.Shutdown();
|
||||||
|
shared_memory->dsp_return_data[0] = it->second.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw;
|
||||||
|
} else {
|
||||||
|
OpusGenericDecodeObject obj{};
|
||||||
|
shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw;
|
||||||
|
decode_objects.insert_or_assign(buffer, obj);
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShutdownMultiStreamDecodeObject: {
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
//[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
shared_memory->dsp_return_data[0] = it->second.Shutdown().raw;
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||||
|
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DecodeInterleavedForMultiStream: {
|
||||||
|
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||||
|
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
auto input_data = shared_memory->host_send_data[1];
|
||||||
|
auto input_data_size = shared_memory->host_send_data[2];
|
||||||
|
auto output_data = shared_memory->host_send_data[3];
|
||||||
|
auto output_data_size = shared_memory->host_send_data[4];
|
||||||
|
//auto final_range = static_cast<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;
|
||||||
|
}
|
||||||
|
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() {
|
OpusDecoder::~OpusDecoder() {
|
||||||
if (!running) {
|
if (dsp_thread.joinable()) {
|
||||||
init_thread.request_stop();
|
// Shutdown the thread
|
||||||
return;
|
auto const stop_token = dsp_thread.get_stop_token();
|
||||||
|
Send(Direction::DSP, Message::Shutdown);
|
||||||
|
auto msg = Receive(Direction::Host, stop_token);
|
||||||
|
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", Message::ShutdownOK, msg);
|
||||||
|
dsp_thread.request_stop();
|
||||||
|
dsp_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown the thread
|
|
||||||
Send(Direction::DSP, Message::Shutdown);
|
|
||||||
auto msg = Receive(Direction::Host);
|
|
||||||
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
|
|
||||||
Message::ShutdownOK, msg);
|
|
||||||
main_thread.request_stop();
|
|
||||||
main_thread.join();
|
|
||||||
running = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpusDecoder::Send(Direction dir, u32 message) {
|
void OpusDecoder::Send(Direction dir, u32 message) {
|
||||||
@@ -64,206 +397,4 @@ u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) {
|
|||||||
return mailbox.Receive(dir, stop_token);
|
return mailbox.Receive(dir, stop_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpusDecoder::Init(std::stop_token stop_token) {
|
|
||||||
Common::SetCurrentThreadName("DSP_OpusDecoder_Init");
|
|
||||||
|
|
||||||
if (Receive(Direction::DSP, stop_token) != Message::Start) {
|
|
||||||
LOG_ERROR(Service_Audio,
|
|
||||||
"DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
main_thread = std::jthread([this](std::stop_token st) { Main(st); });
|
|
||||||
running = true;
|
|
||||||
Send(Direction::Host, Message::StartOK);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpusDecoder::Main(std::stop_token stop_token) {
|
|
||||||
Common::SetCurrentThreadName("DSP_OpusDecoder_Main");
|
|
||||||
|
|
||||||
while (!stop_token.stop_requested()) {
|
|
||||||
auto msg = Receive(Direction::DSP, stop_token);
|
|
||||||
switch (msg) {
|
|
||||||
case Shutdown:
|
|
||||||
Send(Direction::Host, Message::ShutdownOK);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case GetWorkBufferSize: {
|
|
||||||
auto channel_count = static_cast<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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_code == OPUS_OK) {
|
|
||||||
error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
|
|
||||||
input_data, input_data_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_code == OPUS_OK) {
|
|
||||||
if (final_range && decoder_object.GetFinalRange() != final_range) {
|
|
||||||
error_code = OPUS_INVALID_PACKET;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
|
||||||
shared_memory->dsp_return_data[0] = error_code;
|
|
||||||
shared_memory->dsp_return_data[1] = decoded_samples;
|
|
||||||
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::DecodeInterleavedOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case MapMemory: {
|
|
||||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
Send(Direction::Host, Message::MapMemoryOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case UnmapMemory: {
|
|
||||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
Send(Direction::Host, Message::UnmapMemoryOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case GetWorkBufferSizeForMultiStream: {
|
|
||||||
auto total_stream_count = static_cast<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();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_code == OPUS_OK) {
|
|
||||||
error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
|
|
||||||
input_data, input_data_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_code == OPUS_OK) {
|
|
||||||
if (final_range && decoder_object.GetFinalRange() != final_range) {
|
|
||||||
error_code = OPUS_INVALID_PACKET;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
|
||||||
shared_memory->dsp_return_data[0] = error_code;
|
|
||||||
shared_memory->dsp_return_data[1] = decoded_samples;
|
|
||||||
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
} // namespace AudioCore::ADSP::OpusDecoder
|
||||||
|
|||||||
@@ -6,12 +6,13 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
#include "common/container/unordered_map.h"
|
||||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||||
#include "audio_core/adsp/mailbox.h"
|
#include "audio_core/adsp/mailbox.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
@@ -48,16 +49,14 @@ enum Message : u32 {
|
|||||||
DecodeInterleavedForMultiStreamOK = 50,
|
DecodeInterleavedForMultiStreamOK = 50,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/// @brief The AudioRenderer application running on the ADSP.
|
||||||
* The AudioRenderer application running on the ADSP.
|
|
||||||
*/
|
|
||||||
class OpusDecoder {
|
class OpusDecoder {
|
||||||
public:
|
public:
|
||||||
explicit OpusDecoder(Core::System& system);
|
explicit OpusDecoder(Core::System& system);
|
||||||
~OpusDecoder();
|
~OpusDecoder();
|
||||||
|
|
||||||
bool IsRunning() const noexcept {
|
bool IsRunning() const noexcept {
|
||||||
return running;
|
return dsp_thread.joinable();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Send(Direction dir, u32 message);
|
void Send(Direction dir, u32 message);
|
||||||
@@ -68,28 +67,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
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 to communicate messages with the host, drives the main thread
|
||||||
Mailbox mailbox;
|
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,
|
/// 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.
|
/// and the responses are written back by the OpusDecoder.
|
||||||
SharedMemory* shared_memory{};
|
SharedMemory* shared_memory{};
|
||||||
|
std::jthread dsp_thread{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
} // namespace AudioCore::ADSP::OpusDecoder
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
|
|
||||||
#include "common/assert.h"
|
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr u32 OpusStreamCountMax = 255;
|
|
||||||
|
|
||||||
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
|
|
||||||
return total_stream_count > 0 && total_stream_count <= OpusStreamCountMax &&
|
|
||||||
static_cast<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
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// 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
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace AudioCore::ADSP {
|
||||||
|
|
||||||
|
static constexpr u32 DECODE_OBJECT_MAGIC = 0xDEADBEEF;
|
||||||
|
struct LibOpusDecoder {
|
||||||
|
u32 magic;
|
||||||
|
bool initialized;
|
||||||
|
bool state_valid;
|
||||||
|
LibOpusDecoder* self;
|
||||||
|
u32 final_range;
|
||||||
|
void* decoder;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(LibOpusDecoder) == 32);
|
||||||
|
|
||||||
|
static constexpr u32 DECODE_MULTISTREAM_OBJECT_MAGIC = 0xDEADBEEF;
|
||||||
|
struct LibOpusMultistreamDecoder {
|
||||||
|
u32 magic;
|
||||||
|
bool initialized;
|
||||||
|
bool state_valid;
|
||||||
|
LibOpusMultistreamDecoder* self;
|
||||||
|
u32 final_range;
|
||||||
|
void* decoder;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(LibOpusMultistreamDecoder) == 32);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
namespace AudioCore::ADSP::OpusDecoder {
|
||||||
|
|||||||
@@ -10,39 +10,18 @@
|
|||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/audio_core.h"
|
||||||
#include "audio_core/opus/hardware_opus.h"
|
#include "audio_core/opus/hardware_opus.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
namespace AudioCore::OpusDecoder {
|
namespace AudioCore::OpusDecoder {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using namespace Service::Audio;
|
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
|
} // namespace
|
||||||
|
|
||||||
HardwareOpus::HardwareOpus(Core::System& system_)
|
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);
|
opus_decoder.SetSharedMemory(shared_memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +91,7 @@ Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count,
|
|||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
|
Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
|
||||||
@@ -140,7 +119,7 @@ Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 chan
|
|||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
||||||
@@ -154,7 +133,7 @@ Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
|||||||
"Expected Opus shutdown code {}, got {}",
|
"Expected Opus shutdown code {}, got {}",
|
||||||
ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
|
ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
|
||||||
|
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
|
Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
|
||||||
@@ -169,7 +148,7 @@ Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_si
|
|||||||
"Expected Opus shutdown code {}, got {}",
|
"Expected Opus shutdown code {}, got {}",
|
||||||
ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
|
ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
|
||||||
|
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
||||||
@@ -193,12 +172,12 @@ Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
|||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
|
auto error_code = s32(shared_memory.dsp_return_data[0]);
|
||||||
if (error_code == OPUS_OK) {
|
if (error_code == ResultSuccess.raw) {
|
||||||
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
|
out_sample_count = u32(shared_memory.dsp_return_data[1]);
|
||||||
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
||||||
}
|
}
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
|
R_RETURN(Result(u32(error_code)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
|
Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
|
||||||
@@ -207,29 +186,27 @@ Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void
|
|||||||
void* buffer, u64& out_time_taken,
|
void* buffer, u64& out_time_taken,
|
||||||
bool reset) {
|
bool reset) {
|
||||||
std::scoped_lock l{mutex};
|
std::scoped_lock l{mutex};
|
||||||
shared_memory.host_send_data[0] = (u64)buffer;
|
shared_memory.host_send_data[0] = u64(buffer);
|
||||||
shared_memory.host_send_data[1] = (u64)input_data;
|
shared_memory.host_send_data[1] = u64(input_data);
|
||||||
shared_memory.host_send_data[2] = input_data_size;
|
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[4] = output_data_size;
|
||||||
shared_memory.host_send_data[5] = 0;
|
shared_memory.host_send_data[5] = 0;
|
||||||
shared_memory.host_send_data[6] = reset;
|
shared_memory.host_send_data[6] = reset;
|
||||||
|
|
||||||
opus_decoder.Send(ADSP::Direction::DSP,
|
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
|
||||||
ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
|
|
||||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||||
if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) {
|
if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) {
|
||||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
|
||||||
ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
|
|
||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
|
auto const error_code = shared_memory.dsp_return_data[0];
|
||||||
if (error_code == OPUS_OK) {
|
if (error_code == ResultSuccess.raw) {
|
||||||
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
|
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
|
||||||
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
||||||
}
|
}
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
|
Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
|
||||||
@@ -240,8 +217,7 @@ Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
|
|||||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory);
|
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory);
|
||||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||||
if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) {
|
if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) {
|
||||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::MapMemoryOK, msg);
|
||||||
ADSP::OpusDecoder::Message::MapMemoryOK, msg);
|
|
||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
@@ -255,8 +231,7 @@ Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) {
|
|||||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory);
|
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory);
|
||||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||||
if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) {
|
if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) {
|
||||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
|
||||||
ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
|
|
||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <opus.h>
|
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/opus_decoder.h"
|
#include "audio_core/adsp/apps/opus/opus_decoder.h"
|
||||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||||
#include "audio_core/adsp/mailbox.h"
|
#include "audio_core/adsp/mailbox.h"
|
||||||
|
|||||||
@@ -1199,7 +1199,6 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
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)
|
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
|
||||||
|
|
||||||
if (BOOST_NO_HEADERS)
|
if (BOOST_NO_HEADERS)
|
||||||
|
|||||||
@@ -414,9 +414,7 @@ endif()
|
|||||||
if (YUZU_USE_EXTERNAL_FFMPEG)
|
if (YUZU_USE_EXTERNAL_FFMPEG)
|
||||||
add_dependencies(video_core ffmpeg-build)
|
add_dependencies(video_core ffmpeg-build)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(video_core PUBLIC ${FFmpeg_INCLUDE_DIR})
|
target_include_directories(video_core PUBLIC ${FFmpeg_INCLUDE_DIR})
|
||||||
|
|
||||||
target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES})
|
target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES})
|
||||||
target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})
|
target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user