summaryrefslogtreecommitdiffstats
path: root/src/audio_core/adsp/apps
diff options
context:
space:
mode:
Diffstat (limited to 'src/audio_core/adsp/apps')
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp48
-rw-r--r--src/audio_core/adsp/apps/audio_renderer/audio_renderer.h45
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decode_object.cpp107
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decode_object.h38
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decoder.cpp269
-rw-r--r--src/audio_core/adsp/apps/opus/opus_decoder.h92
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp111
-rw-r--r--src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h39
-rw-r--r--src/audio_core/adsp/apps/opus/shared_memory.h17
9 files changed, 715 insertions, 51 deletions
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
index 2e549bc6f..972d5e45b 100644
--- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.cpp
@@ -14,13 +14,12 @@
#include "core/core.h"
#include "core/core_timing.h"
-MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP", MP_RGB(60, 19, 97));
+MICROPROFILE_DEFINE(Audio_Renderer, "Audio", "DSP_AudioRenderer", MP_RGB(60, 19, 97));
namespace AudioCore::ADSP::AudioRenderer {
-AudioRenderer::AudioRenderer(Core::System& system_, Core::Memory::Memory& memory_,
- Sink::Sink& sink_)
- : system{system_}, memory{memory_}, sink{sink_} {}
+AudioRenderer::AudioRenderer(Core::System& system_, Sink::Sink& sink_)
+ : system{system_}, sink{sink_} {}
AudioRenderer::~AudioRenderer() {
Stop();
@@ -33,8 +32,8 @@ void AudioRenderer::Start() {
main_thread = std::jthread([this](std::stop_token stop_token) { Main(stop_token); });
- mailbox.Send(Direction::DSP, {Message::InitializeOK, {}});
- if (mailbox.Receive(Direction::Host).msg != Message::InitializeOK) {
+ mailbox.Send(Direction::DSP, Message::InitializeOK);
+ if (mailbox.Receive(Direction::Host) != Message::InitializeOK) {
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
"message response from ADSP!");
return;
@@ -47,8 +46,8 @@ void AudioRenderer::Stop() {
return;
}
- mailbox.Send(Direction::DSP, {Message::Shutdown, {}});
- if (mailbox.Receive(Direction::Host).msg != Message::Shutdown) {
+ mailbox.Send(Direction::DSP, Message::Shutdown);
+ if (mailbox.Receive(Direction::Host) != Message::Shutdown) {
LOG_ERROR(Service_Audio, "Host Audio Renderer -- Failed to receive shutdown "
"message response from ADSP!");
}
@@ -67,25 +66,25 @@ void AudioRenderer::Stop() {
void AudioRenderer::Signal() {
signalled_tick = system.CoreTiming().GetGlobalTimeNs().count();
- Send(Direction::DSP, {Message::Render, {}});
+ Send(Direction::DSP, Message::Render);
}
void AudioRenderer::Wait() {
- auto received = Receive(Direction::Host);
- if (received.msg != Message::RenderResponse) {
+ auto msg = Receive(Direction::Host);
+ if (msg != Message::RenderResponse) {
LOG_ERROR(Service_Audio,
"Did not receive the expected render response from the AudioRenderer! Expected "
"{}, got {}",
- Message::RenderResponse, received.msg);
+ Message::RenderResponse, msg);
}
}
-void AudioRenderer::Send(Direction dir, MailboxMessage message) {
+void AudioRenderer::Send(Direction dir, u32 message) {
mailbox.Send(dir, std::move(message));
}
-MailboxMessage AudioRenderer::Receive(Direction dir, bool block) {
- return mailbox.Receive(dir, block);
+u32 AudioRenderer::Receive(Direction dir) {
+ return mailbox.Receive(dir);
}
void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
@@ -120,7 +119,7 @@ void AudioRenderer::CreateSinkStreams() {
}
void AudioRenderer::Main(std::stop_token stop_token) {
- static constexpr char name[]{"AudioRenderer"};
+ static constexpr char name[]{"DSP_AudioRenderer_Main"};
MicroProfileOnThreadCreate(name);
Common::SetCurrentThreadName(name);
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
@@ -128,28 +127,28 @@ void AudioRenderer::Main(std::stop_token stop_token) {
// TODO: Create buffer map/unmap thread + mailbox
// TODO: Create gMix devices, initialize them here
- if (mailbox.Receive(Direction::DSP).msg != Message::InitializeOK) {
+ if (mailbox.Receive(Direction::DSP) != Message::InitializeOK) {
LOG_ERROR(Service_Audio,
"ADSP Audio Renderer -- Failed to receive initialize message from host!");
return;
}
- mailbox.Send(Direction::Host, {Message::InitializeOK, {}});
+ mailbox.Send(Direction::Host, Message::InitializeOK);
// 0.12 seconds (2,304,000 / 19,200,000)
constexpr u64 max_process_time{2'304'000ULL};
while (!stop_token.stop_requested()) {
- auto received{mailbox.Receive(Direction::DSP)};
- switch (received.msg) {
+ auto msg{mailbox.Receive(Direction::DSP)};
+ switch (msg) {
case Message::Shutdown:
- mailbox.Send(Direction::Host, {Message::Shutdown, {}});
+ mailbox.Send(Direction::Host, Message::Shutdown);
return;
case Message::Render: {
if (system.IsShuttingDown()) [[unlikely]] {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
- mailbox.Send(Direction::Host, {Message::RenderResponse, {}});
+ mailbox.Send(Direction::Host, Message::RenderResponse);
continue;
}
std::array<bool, MaxRendererSessions> buffers_reset{};
@@ -205,13 +204,12 @@ void AudioRenderer::Main(std::stop_token stop_token) {
}
}
- mailbox.Send(Direction::Host, {Message::RenderResponse, {}});
+ mailbox.Send(Direction::Host, Message::RenderResponse);
} break;
default:
LOG_WARNING(Service_Audio,
- "ADSP AudioRenderer received an invalid message, msg={:02X}!",
- received.msg);
+ "ADSP AudioRenderer received an invalid message, msg={:02X}!", msg);
break;
}
}
diff --git a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
index 3f5b7dca2..85874d88a 100644
--- a/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
+++ b/src/audio_core/adsp/apps/audio_renderer/audio_renderer.h
@@ -17,13 +17,6 @@
namespace Core {
class System;
-namespace Timing {
-struct EventType;
-}
-namespace Memory {
-class Memory;
-}
-class System;
} // namespace Core
namespace AudioCore {
@@ -34,19 +27,19 @@ class Sink;
namespace ADSP::AudioRenderer {
enum Message : u32 {
- Invalid = 0x00,
- MapUnmap_Map = 0x01,
- MapUnmap_MapResponse = 0x02,
- MapUnmap_Unmap = 0x03,
- MapUnmap_UnmapResponse = 0x04,
- MapUnmap_InvalidateCache = 0x05,
- MapUnmap_InvalidateCacheResponse = 0x06,
- MapUnmap_Shutdown = 0x07,
- MapUnmap_ShutdownResponse = 0x08,
- InitializeOK = 0x16,
- RenderResponse = 0x20,
- Render = 0x2A,
- Shutdown = 0x34,
+ Invalid = 0,
+ MapUnmap_Map = 1,
+ MapUnmap_MapResponse = 2,
+ MapUnmap_Unmap = 3,
+ MapUnmap_UnmapResponse = 4,
+ MapUnmap_InvalidateCache = 5,
+ MapUnmap_InvalidateCacheResponse = 6,
+ MapUnmap_Shutdown = 7,
+ MapUnmap_ShutdownResponse = 8,
+ InitializeOK = 22,
+ RenderResponse = 32,
+ Render = 42,
+ Shutdown = 52,
};
/**
@@ -54,7 +47,7 @@ enum Message : u32 {
*/
class AudioRenderer {
public:
- explicit AudioRenderer(Core::System& system, Core::Memory::Memory& memory, Sink::Sink& sink);
+ explicit AudioRenderer(Core::System& system, Sink::Sink& sink);
~AudioRenderer();
/**
@@ -72,8 +65,8 @@ public:
void Signal();
void Wait();
- void Send(Direction dir, MailboxMessage message);
- MailboxMessage Receive(Direction dir, bool block = true);
+ void Send(Direction dir, u32 message);
+ u32 Receive(Direction dir);
void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
u64 applet_resource_user_id, bool reset) noexcept;
@@ -94,9 +87,7 @@ private:
/// Core system
Core::System& system;
- /// Memory
- Core::Memory::Memory& memory;
- /// The output sink the AudioRenderer will use
+ /// The output sink the AudioRenderer will send samples to
Sink::Sink& sink;
/// The active mailbox
Mailbox mailbox;
@@ -104,11 +95,13 @@ private:
std::jthread main_thread{};
/// The current state
std::atomic<bool> running{};
+ /// Shared memory of input command buffers, set by host, read by DSP
std::array<CommandBuffer, MaxRendererSessions> command_buffers{};
/// The command lists to process
std::array<CommandListProcessor, MaxRendererSessions> command_list_processors{};
/// The streams which will receive the processed samples
std::array<Sink::SinkStream*, MaxRendererSessions> streams{};
+ /// CPU Tick when the DSP was signalled to process, uses time rather than tick
u64 signalled_tick{0};
};
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 000000000..2c16d3769
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_decode_object.cpp
@@ -0,0 +1,107 @@
+// 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 000000000..6425f987c
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_decode_object.h
@@ -0,0 +1,38 @@
+// 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
new file mode 100644
index 000000000..2084de128
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_decoder.cpp
@@ -0,0 +1,269 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <chrono>
+
+#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/log.h"
+#include "common/microprofile.h"
+#include "common/thread.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+
+MICROPROFILE_DEFINE(OpusDecoder, "Audio", "DSP_OpusDecoder", MP_RGB(60, 19, 97));
+
+namespace AudioCore::ADSP::OpusDecoder {
+
+namespace {
+constexpr size_t OpusStreamCountMax = 255;
+
+bool IsValidChannelCount(u32 channel_count) {
+ return channel_count == 1 || channel_count == 2;
+}
+
+bool IsValidMultiStreamChannelCount(u32 channel_count) {
+ return channel_count <= OpusStreamCountMax;
+}
+
+bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 sterero_stream_count) {
+ return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 &&
+ sterero_stream_count > 0 && sterero_stream_count <= total_stream_count;
+}
+} // namespace
+
+OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
+ init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); });
+}
+
+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;
+}
+
+void OpusDecoder::Send(Direction dir, u32 message) {
+ mailbox.Send(dir, std::move(message));
+}
+
+u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) {
+ return mailbox.Receive(dir, stop_token);
+}
+
+void OpusDecoder::Init(std::stop_token stop_token) {
+ Common::SetCurrentThreadName("DSP_OpusDecoder_Init");
+
+ if (Receive(Direction::DSP, stop_token) != Message::Start) {
+ LOG_ERROR(Service_Audio,
+ "DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
+ return;
+ }
+ main_thread = std::jthread([this](std::stop_token st) { Main(st); });
+ running = true;
+ Send(Direction::Host, Message::StartOK);
+}
+
+void OpusDecoder::Main(std::stop_token stop_token) {
+ Common::SetCurrentThreadName("DSP_OpusDecoder_Main");
+
+ while (!stop_token.stop_requested()) {
+ auto msg = Receive(Direction::DSP, stop_token);
+ switch (msg) {
+ case Shutdown:
+ Send(Direction::Host, Message::ShutdownOK);
+ return;
+
+ case GetWorkBufferSize: {
+ auto channel_count = static_cast<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
diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.h b/src/audio_core/adsp/apps/opus/opus_decoder.h
new file mode 100644
index 000000000..fcb89bb40
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_decoder.h
@@ -0,0 +1,92 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <thread>
+
+#include "audio_core/adsp/apps/opus/shared_memory.h"
+#include "audio_core/adsp/mailbox.h"
+#include "common/common_types.h"
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace AudioCore::ADSP::OpusDecoder {
+
+enum Message : u32 {
+ Invalid = 0,
+ Start = 1,
+ Shutdown = 2,
+ StartOK = 11,
+ ShutdownOK = 12,
+ GetWorkBufferSize = 21,
+ InitializeDecodeObject = 22,
+ ShutdownDecodeObject = 23,
+ DecodeInterleaved = 24,
+ MapMemory = 25,
+ UnmapMemory = 26,
+ GetWorkBufferSizeForMultiStream = 27,
+ InitializeMultiStreamDecodeObject = 28,
+ ShutdownMultiStreamDecodeObject = 29,
+ DecodeInterleavedForMultiStream = 30,
+
+ GetWorkBufferSizeOK = 41,
+ InitializeDecodeObjectOK = 42,
+ ShutdownDecodeObjectOK = 43,
+ DecodeInterleavedOK = 44,
+ MapMemoryOK = 45,
+ UnmapMemoryOK = 46,
+ GetWorkBufferSizeForMultiStreamOK = 47,
+ InitializeMultiStreamDecodeObjectOK = 48,
+ ShutdownMultiStreamDecodeObjectOK = 49,
+ DecodeInterleavedForMultiStreamOK = 50,
+};
+
+/**
+ * The AudioRenderer application running on the ADSP.
+ */
+class OpusDecoder {
+public:
+ explicit OpusDecoder(Core::System& system);
+ ~OpusDecoder();
+
+ bool IsRunning() const noexcept {
+ return running;
+ }
+
+ void Send(Direction dir, u32 message);
+ u32 Receive(Direction dir, std::stop_token stop_token = {});
+
+ void SetSharedMemory(SharedMemory& shared_memory_) {
+ shared_memory = &shared_memory_;
+ }
+
+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{};
+};
+
+} // 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 000000000..f6d362e68
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp
@@ -0,0 +1,111 @@
+// 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 {
+bool IsValidChannelCount(u32 channel_count) {
+ return channel_count == 1 || channel_count == 2;
+}
+
+bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
+ return total_stream_count > 0 && stereo_stream_count > 0 &&
+ stereo_stream_count <= total_stream_count && IsValidChannelCount(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 000000000..93558ded5
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h
@@ -0,0 +1,39 @@
+// 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/shared_memory.h b/src/audio_core/adsp/apps/opus/shared_memory.h
new file mode 100644
index 000000000..c696731ed
--- /dev/null
+++ b/src/audio_core/adsp/apps/opus/shared_memory.h
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace AudioCore::ADSP::OpusDecoder {
+
+struct SharedMemory {
+ std::array<u8, 0x100> channel_mapping{};
+ std::array<u64, 16> host_send_data{};
+ std::array<u64, 16> dsp_return_data{};
+};
+
+} // namespace AudioCore::ADSP::OpusDecoder