aboutsummaryrefslogtreecommitdiff
path: root/src
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 /src
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>
Diffstat (limited to 'src')
-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
15 files changed, 659 insertions, 418 deletions
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})