From 67e2d5c28b8423c4f3f1d5b00f87325684158a6f Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Thu, 31 Aug 2023 15:09:15 +0100 Subject: Reimplement HardwareOpus --- src/audio_core/CMakeLists.txt | 16 +- src/audio_core/adsp/adsp.cpp | 13 +- src/audio_core/adsp/adsp.h | 3 + .../adsp/apps/audio_renderer/audio_renderer.cpp | 48 ++-- .../adsp/apps/audio_renderer/audio_renderer.h | 45 ++-- .../adsp/apps/opus/opus_decode_object.cpp | 107 ++++++++ src/audio_core/adsp/apps/opus/opus_decode_object.h | 38 +++ src/audio_core/adsp/apps/opus/opus_decoder.cpp | 269 +++++++++++++++++++++ src/audio_core/adsp/apps/opus/opus_decoder.h | 92 +++++++ .../apps/opus/opus_multistream_decode_object.cpp | 111 +++++++++ .../apps/opus/opus_multistream_decode_object.h | 39 +++ src/audio_core/adsp/apps/opus/shared_memory.h | 17 ++ src/audio_core/adsp/mailbox.h | 27 +-- src/audio_core/opus/decoder.cpp | 179 ++++++++++++++ src/audio_core/opus/decoder.h | 53 ++++ src/audio_core/opus/decoder_manager.cpp | 102 ++++++++ src/audio_core/opus/decoder_manager.h | 38 +++ src/audio_core/opus/hardware_opus.cpp | 241 ++++++++++++++++++ src/audio_core/opus/hardware_opus.h | 45 ++++ src/audio_core/opus/parameters.h | 54 +++++ 20 files changed, 1465 insertions(+), 72 deletions(-) create mode 100644 src/audio_core/adsp/apps/opus/opus_decode_object.cpp create mode 100644 src/audio_core/adsp/apps/opus/opus_decode_object.h create mode 100644 src/audio_core/adsp/apps/opus/opus_decoder.cpp create mode 100644 src/audio_core/adsp/apps/opus/opus_decoder.h create mode 100644 src/audio_core/adsp/apps/opus/opus_multistream_decode_object.cpp create mode 100644 src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h create mode 100644 src/audio_core/adsp/apps/opus/shared_memory.h create mode 100644 src/audio_core/opus/decoder.cpp create mode 100644 src/audio_core/opus/decoder.h create mode 100644 src/audio_core/opus/decoder_manager.cpp create mode 100644 src/audio_core/opus/decoder_manager.h create mode 100644 src/audio_core/opus/hardware_opus.cpp create mode 100644 src/audio_core/opus/hardware_opus.h create mode 100644 src/audio_core/opus/parameters.h (limited to 'src/audio_core') diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 67dfe0290..400988c5f 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -10,6 +10,13 @@ add_library(audio_core STATIC adsp/apps/audio_renderer/command_buffer.h adsp/apps/audio_renderer/command_list_processor.cpp 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 audio_event.h @@ -35,6 +42,13 @@ add_library(audio_core STATIC in/audio_in.h in/audio_in_system.cpp in/audio_in_system.h + opus/hardware_opus.cpp + opus/hardware_opus.h + opus/decoder_manager.cpp + opus/decoder_manager.h + opus/decoder.cpp + opus/decoder.h + opus/parameters.h out/audio_out.cpp out/audio_out.h out/audio_out_system.cpp @@ -214,7 +228,7 @@ else() ) endif() -target_link_libraries(audio_core PUBLIC common core) +target_link_libraries(audio_core PUBLIC common core Opus::opus) if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) target_link_libraries(audio_core PRIVATE dynarmic::dynarmic) endif() diff --git a/src/audio_core/adsp/adsp.cpp b/src/audio_core/adsp/adsp.cpp index 0580990f5..6c53c98fd 100644 --- a/src/audio_core/adsp/adsp.cpp +++ b/src/audio_core/adsp/adsp.cpp @@ -7,12 +7,21 @@ namespace AudioCore::ADSP { ADSP::ADSP(Core::System& system, Sink::Sink& sink) { - audio_renderer = - std::make_unique(system, system.ApplicationMemory(), sink); + audio_renderer = std::make_unique(system, sink); + opus_decoder = std::make_unique(system); + opus_decoder->Send(Direction::DSP, OpusDecoder::Message::Start); + if (opus_decoder->Receive(Direction::Host) != OpusDecoder::Message::StartOK) { + LOG_ERROR(Service_Audio, "OpusDeocder failed to initialize."); + return; + } } AudioRenderer::AudioRenderer& ADSP::AudioRenderer() { return *audio_renderer.get(); } +OpusDecoder::OpusDecoder& ADSP::OpusDecoder() { + return *opus_decoder.get(); +} + } // namespace AudioCore::ADSP diff --git a/src/audio_core/adsp/adsp.h b/src/audio_core/adsp/adsp.h index bd5bcc63b..a0c24a16a 100644 --- a/src/audio_core/adsp/adsp.h +++ b/src/audio_core/adsp/adsp.h @@ -4,6 +4,7 @@ #pragma once #include "audio_core/adsp/apps/audio_renderer/audio_renderer.h" +#include "audio_core/adsp/apps/opus/opus_decoder.h" #include "common/common_types.h" namespace Core { @@ -40,10 +41,12 @@ public: ~ADSP() = default; AudioRenderer::AudioRenderer& AudioRenderer(); + OpusDecoder::OpusDecoder& OpusDecoder(); private: /// AudioRenderer app std::unique_ptr audio_renderer{}; + std::unique_ptr opus_decoder{}; }; } // namespace ADSP 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 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 running{}; + /// Shared memory of input command buffers, set by host, read by DSP std::array command_buffers{}; /// The command lists to process std::array command_list_processors{}; /// The streams which will receive the processed samples std::array 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(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count); +} + +OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) { + auto* new_decoder = reinterpret_cast(buffer); + auto* comparison = reinterpret_cast(buffer2); + + if (new_decoder->magic == DecodeObjectMagic) { + if (!new_decoder->initialized || + (new_decoder->initialized && new_decoder->self == comparison)) { + new_decoder->state_valid = true; + } + } else { + new_decoder->initialized = false; + new_decoder->state_valid = true; + } + return *new_decoder; +} + +s32 OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) { + if (!state_valid) { + return OPUS_INVALID_STATE; + } + + if (initialized) { + return OPUS_OK; + } + + // Unfortunately libopus does not expose the OpusDecoder struct publicly, so we can't include + // it in this class. Nintendo does not allocate memory, which is why we have a workbuffer + // provided. + // We could use _create and have libopus allocate it for us, but then we have to separately + // track which decoder is being used between this and multistream in order to call the correct + // destroy from the host side. + // This is a bit cringe, but is safe as these objects are only ever initialized inside the given + // workbuffer, and GetWorkBufferSize will guarantee there's enough space to follow. + decoder = (LibOpusDecoder*)(this + 1); + s32 ret = opus_decoder_init(decoder, sample_rate, channel_count); + if (ret == OPUS_OK) { + magic = DecodeObjectMagic; + initialized = true; + state_valid = true; + self = this; + final_range = 0; + } + return ret; +} + +s32 OpusDecodeObject::Shutdown() { + if (!state_valid) { + return OPUS_INVALID_STATE; + } + + if (initialized) { + magic = 0x0; + initialized = false; + state_valid = false; + self = nullptr; + final_range = 0; + decoder = nullptr; + } + return OPUS_OK; +} + +s32 OpusDecodeObject::ResetDecoder() { + return opus_decoder_ctl(decoder, OPUS_RESET_STATE); +} + +s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, + u64 input_data, u64 input_data_size) { + ASSERT(initialized); + out_sample_count = 0; + + if (!state_valid) { + return OPUS_INVALID_STATE; + } + + auto ret_code_or_samples = opus_decode( + decoder, reinterpret_cast(input_data), static_cast(input_data_size), + reinterpret_cast(output_data), static_cast(output_data_size), 0); + + if (ret_code_or_samples < OPUS_OK) { + return ret_code_or_samples; + } + + out_sample_count = ret_code_or_samples; + return opus_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range); +} + +} // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/opus_decode_object.h b/src/audio_core/adsp/apps/opus/opus_decode_object.h new file mode 100644 index 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 + +#include "common/common_types.h" + +namespace AudioCore::ADSP::OpusDecoder { +using LibOpusDecoder = ::OpusDecoder; +static constexpr u32 DecodeObjectMagic = 0xDEADBEEF; + +class OpusDecodeObject { +public: + static u32 GetWorkBufferSize(u32 channel_count); + static OpusDecodeObject& Initialize(u64 buffer, u64 buffer2); + + s32 InitializeDecoder(u32 sample_rate, u32 channel_count); + s32 Shutdown(); + s32 ResetDecoder(); + s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, + u64 input_data_size); + u32 GetFinalRange() const noexcept { + return final_range; + } + +private: + u32 magic; + bool initialized; + bool state_valid; + OpusDecodeObject* self; + u32 final_range; + LibOpusDecoder* decoder; +}; +static_assert(std::is_trivially_constructible_v); + +} // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/opus_decoder.cpp b/src/audio_core/adsp/apps/opus/opus_decoder.cpp 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 +#include + +#include "audio_core/adsp/apps/opus/opus_decode_object.h" +#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h" +#include "audio_core/adsp/apps/opus/shared_memory.h" +#include "audio_core/audio_core.h" +#include "audio_core/common/common.h" +#include "common/logging/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(shared_memory->host_send_data[0]); + + ASSERT(IsValidChannelCount(channel_count)); + + shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count); + Send(Direction::Host, Message::GetWorkBufferSizeOK); + } break; + + case InitializeDecodeObject: { + auto buffer = shared_memory->host_send_data[0]; + auto buffer_size = shared_memory->host_send_data[1]; + auto sample_rate = static_cast(shared_memory->host_send_data[2]); + auto channel_count = static_cast(shared_memory->host_send_data[3]); + + ASSERT(sample_rate >= 0); + ASSERT(IsValidChannelCount(channel_count)); + ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count)); + + auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); + shared_memory->dsp_return_data[0] = + decoder_object.InitializeDecoder(sample_rate, channel_count); + + Send(Direction::Host, Message::InitializeDecodeObjectOK); + } break; + + case ShutdownDecodeObject: { + auto buffer = shared_memory->host_send_data[0]; + [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; + + auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); + shared_memory->dsp_return_data[0] = decoder_object.Shutdown(); + + Send(Direction::Host, Message::ShutdownDecodeObjectOK); + } break; + + case DecodeInterleaved: { + auto start_time = system.CoreTiming().GetGlobalTimeUs(); + + auto buffer = shared_memory->host_send_data[0]; + auto input_data = shared_memory->host_send_data[1]; + auto input_data_size = shared_memory->host_send_data[2]; + auto output_data = shared_memory->host_send_data[3]; + auto output_data_size = shared_memory->host_send_data[4]; + auto final_range = static_cast(shared_memory->host_send_data[5]); + auto reset_requested = shared_memory->host_send_data[6]; + + u32 decoded_samples{0}; + + auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer); + s32 error_code{OPUS_OK}; + if (reset_requested) { + error_code = decoder_object.ResetDecoder(); + } + + 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(shared_memory->host_send_data[0]); + auto stereo_stream_count = static_cast(shared_memory->host_send_data[1]); + + ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count)); + + shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize( + total_stream_count, stereo_stream_count); + Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK); + } break; + + case InitializeMultiStreamDecodeObject: { + auto buffer = shared_memory->host_send_data[0]; + auto buffer_size = shared_memory->host_send_data[1]; + auto sample_rate = static_cast(shared_memory->host_send_data[2]); + auto channel_count = static_cast(shared_memory->host_send_data[3]); + auto total_stream_count = static_cast(shared_memory->host_send_data[4]); + auto stereo_stream_count = static_cast(shared_memory->host_send_data[5]); + // Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel + // mappings, but [6] is never set, and there is not enough room in the argument data for + // more than 40 channels, when 255 are possible. + // It also means the mapping values are undefined, though likely always 0, + // and the mappings given by the game are ignored. The mappings are copied to this + // dedicated buffer host side, so let's do as intended. + auto mappings = shared_memory->channel_mapping.data(); + + ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count)); + ASSERT(sample_rate >= 0); + ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize( + total_stream_count, stereo_stream_count)); + + auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); + shared_memory->dsp_return_data[0] = decoder_object.InitializeDecoder( + sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings); + + Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK); + } break; + + case ShutdownMultiStreamDecodeObject: { + auto buffer = shared_memory->host_send_data[0]; + [[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1]; + + auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); + shared_memory->dsp_return_data[0] = decoder_object.Shutdown(); + + Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK); + } break; + + case DecodeInterleavedForMultiStream: { + auto start_time = system.CoreTiming().GetGlobalTimeUs(); + + auto buffer = shared_memory->host_send_data[0]; + auto input_data = shared_memory->host_send_data[1]; + auto input_data_size = shared_memory->host_send_data[2]; + auto output_data = shared_memory->host_send_data[3]; + auto output_data_size = shared_memory->host_send_data[4]; + auto final_range = static_cast(shared_memory->host_send_data[5]); + auto reset_requested = shared_memory->host_send_data[6]; + + u32 decoded_samples{0}; + + auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer); + s32 error_code{OPUS_OK}; + if (reset_requested) { + error_code = decoder_object.ResetDecoder(); + } + + 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 +#include + +#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(sizeof(OpusMultiStreamDecodeObject)) + + opus_multistream_decoder_get_size(total_stream_count, stereo_stream_count); + } + return 0; +} + +OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) { + auto* new_decoder = reinterpret_cast(buffer); + auto* comparison = reinterpret_cast(buffer2); + + if (new_decoder->magic == DecodeMultiStreamObjectMagic) { + if (!new_decoder->initialized || + (new_decoder->initialized && new_decoder->self == comparison)) { + new_decoder->state_valid = true; + } + } else { + new_decoder->initialized = false; + new_decoder->state_valid = true; + } + return *new_decoder; +} + +s32 OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count, + u32 channel_count, u32 stereo_stream_count, + u8* mappings) { + if (!state_valid) { + return OPUS_INVALID_STATE; + } + + if (initialized) { + return OPUS_OK; + } + + // See OpusDecodeObject::InitializeDecoder for an explanation of this + decoder = (LibOpusMSDecoder*)(this + 1); + s32 ret = opus_multistream_decoder_init(decoder, sample_rate, channel_count, total_stream_count, + stereo_stream_count, mappings); + if (ret == OPUS_OK) { + magic = DecodeMultiStreamObjectMagic; + initialized = true; + state_valid = true; + self = this; + final_range = 0; + } + return ret; +} + +s32 OpusMultiStreamDecodeObject::Shutdown() { + if (!state_valid) { + return OPUS_INVALID_STATE; + } + + if (initialized) { + magic = 0x0; + initialized = false; + state_valid = false; + self = nullptr; + final_range = 0; + decoder = nullptr; + } + return OPUS_OK; +} + +s32 OpusMultiStreamDecodeObject::ResetDecoder() { + return opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE); +} + +s32 OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data, + u64 output_data_size, u64 input_data, u64 input_data_size) { + ASSERT(initialized); + out_sample_count = 0; + + if (!state_valid) { + return OPUS_INVALID_STATE; + } + + auto ret_code_or_samples = opus_multistream_decode( + decoder, reinterpret_cast(input_data), static_cast(input_data_size), + reinterpret_cast(output_data), static_cast(output_data_size), 0); + + if (ret_code_or_samples < OPUS_OK) { + return ret_code_or_samples; + } + + out_sample_count = ret_code_or_samples; + return opus_multistream_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range); +} + +} // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h b/src/audio_core/adsp/apps/opus/opus_multistream_decode_object.h new file mode 100644 index 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 + +#include "common/common_types.h" + +namespace AudioCore::ADSP::OpusDecoder { +using LibOpusMSDecoder = ::OpusMSDecoder; +static constexpr u32 DecodeMultiStreamObjectMagic = 0xDEADBEEF; + +class OpusMultiStreamDecodeObject { +public: + static u32 GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count); + static OpusMultiStreamDecodeObject& Initialize(u64 buffer, u64 buffer2); + + s32 InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, + u32 stereo_stream_count, u8* mappings); + s32 Shutdown(); + s32 ResetDecoder(); + s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, + u64 input_data_size); + u32 GetFinalRange() const noexcept { + return final_range; + } + +private: + u32 magic; + bool initialized; + bool state_valid; + OpusMultiStreamDecodeObject* self; + u32 final_range; + LibOpusMSDecoder* decoder; +}; +static_assert(std::is_trivially_constructible_v); + +} // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/apps/opus/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 channel_mapping{}; + std::array host_send_data{}; + std::array dsp_return_data{}; +}; + +} // namespace AudioCore::ADSP::OpusDecoder diff --git a/src/audio_core/adsp/mailbox.h b/src/audio_core/adsp/mailbox.h index c31b73717..1dd40ebfa 100644 --- a/src/audio_core/adsp/mailbox.h +++ b/src/audio_core/adsp/mailbox.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "common/bounded_threadsafe_queue.h" #include "common/common_types.h" @@ -19,11 +21,6 @@ enum class Direction : u32 { DSP, }; -struct MailboxMessage { - u32 msg; - std::span data; -}; - class Mailbox { public: void Initialize(AppMailboxId id_) { @@ -35,25 +32,19 @@ public: return id; } - void Send(Direction dir, MailboxMessage&& message) { + void Send(Direction dir, u32 message) { auto& queue = dir == Direction::Host ? host_queue : adsp_queue; - queue.EmplaceWait(std::move(message)); + queue.EmplaceWait(message); } - MailboxMessage Receive(Direction dir, bool block = true) { + u32 Receive(Direction dir, std::stop_token stop_token = {}) { auto& queue = dir == Direction::Host ? host_queue : adsp_queue; - MailboxMessage t; - if (block) { - queue.PopWait(t); - } else { - queue.TryPop(t); - } - return t; + return queue.PopWait(stop_token); } void Reset() { id = AppMailboxId::Invalid; - MailboxMessage t; + u32 t{}; while (host_queue.TryPop(t)) { } while (adsp_queue.TryPop(t)) { @@ -62,8 +53,8 @@ public: private: AppMailboxId id{0}; - Common::SPSCQueue host_queue; - Common::SPSCQueue adsp_queue; + Common::SPSCQueue host_queue; + Common::SPSCQueue adsp_queue; }; } // namespace AudioCore::ADSP diff --git a/src/audio_core/opus/decoder.cpp b/src/audio_core/opus/decoder.cpp new file mode 100644 index 000000000..5b23fce14 --- /dev/null +++ b/src/audio_core/opus/decoder.cpp @@ -0,0 +1,179 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/opus/decoder.h" +#include "audio_core/opus/hardware_opus.h" +#include "audio_core/opus/parameters.h" +#include "common/alignment.h" +#include "common/swap.h" +#include "core/core.h" + +namespace AudioCore::OpusDecoder { +using namespace Service::Audio; +namespace { +OpusPacketHeader ReverseHeader(OpusPacketHeader header) { + OpusPacketHeader out; + out.size = Common::swap32(header.size); + out.final_range = Common::swap32(header.final_range); + return out; +} +} // namespace + +OpusDecoder::OpusDecoder(Core::System& system_, HardwareOpus& hardware_opus_) + : system{system_}, hardware_opus{hardware_opus_} {} + +OpusDecoder::~OpusDecoder() { + if (decode_object_initialized) { + hardware_opus.ShutdownDecodeObject(shared_buffer.get(), shared_buffer_size); + } +} + +Result OpusDecoder::Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, + u64 transfer_memory_size) { + auto frame_size{params.use_large_frame_size ? 5760 : 1920}; + shared_buffer_size = transfer_memory_size; + shared_buffer = std::make_unique(shared_buffer_size); + shared_memory_mapped = true; + + buffer_size = + Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16); + + out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size}; + size_t in_data_size{0x600u}; + in_data = {out_data.data() - in_data_size, in_data_size}; + + ON_RESULT_FAILURE { + if (shared_memory_mapped) { + shared_memory_mapped = false; + ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size))); + } + }; + + R_TRY(hardware_opus.InitializeDecodeObject(params.sample_rate, params.channel_count, + shared_buffer.get(), shared_buffer_size)); + + sample_rate = params.sample_rate; + channel_count = params.channel_count; + use_large_frame_size = params.use_large_frame_size; + decode_object_initialized = true; + R_SUCCEED(); +} + +Result OpusDecoder::Initialize(OpusMultiStreamParametersEx& params, + Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size) { + auto frame_size{params.use_large_frame_size ? 5760 : 1920}; + shared_buffer_size = transfer_memory_size; + shared_buffer = std::make_unique(shared_buffer_size); + shared_memory_mapped = true; + + buffer_size = + Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 16); + + out_data = {shared_buffer.get() + shared_buffer_size - buffer_size, buffer_size}; + size_t in_data_size{Common::AlignUp(1500ull * params.total_stream_count, 64u)}; + in_data = {out_data.data() - in_data_size, in_data_size}; + + ON_RESULT_FAILURE { + if (shared_memory_mapped) { + shared_memory_mapped = false; + ASSERT(R_SUCCEEDED(hardware_opus.UnmapMemory(shared_buffer.get(), shared_buffer_size))); + } + }; + + R_TRY(hardware_opus.InitializeMultiStreamDecodeObject( + params.sample_rate, params.channel_count, params.total_stream_count, + params.stereo_stream_count, params.mappings.data(), shared_buffer.get(), + shared_buffer_size)); + + sample_rate = params.sample_rate; + channel_count = params.channel_count; + total_stream_count = params.total_stream_count; + stereo_stream_count = params.stereo_stream_count; + use_large_frame_size = params.use_large_frame_size; + decode_object_initialized = true; + R_SUCCEED(); +} + +Result OpusDecoder::DecodeInterleaved(u32* out_data_size, u64* out_time_taken, + u32* out_sample_count, std::span input_data, + std::span output_data, bool reset) { + u32 out_samples; + u64 time_taken{}; + + R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall); + + auto* header_p{reinterpret_cast(input_data.data())}; + OpusPacketHeader header{ReverseHeader(*header_p)}; + + R_UNLESS(in_data.size_bytes() >= header.size && + header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(), + ResultBufferTooSmall); + + if (!shared_memory_mapped) { + R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size)); + shared_memory_mapped = true; + } + + std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size); + + R_TRY(hardware_opus.DecodeInterleaved(out_samples, out_data.data(), out_data.size_bytes(), + channel_count, in_data.data(), header.size, + shared_buffer.get(), time_taken, reset)); + + std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16)); + + *out_data_size = header.size + sizeof(OpusPacketHeader); + *out_sample_count = out_samples; + if (out_time_taken) { + *out_time_taken = time_taken / 1000; + } + R_SUCCEED(); +} + +Result OpusDecoder::SetContext([[maybe_unused]] std::span context) { + R_SUCCEED_IF(shared_memory_mapped); + shared_memory_mapped = true; + R_RETURN(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size)); +} + +Result OpusDecoder::DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken, + u32* out_sample_count, + std::span input_data, + std::span output_data, bool reset) { + u32 out_samples; + u64 time_taken{}; + + R_UNLESS(input_data.size_bytes() > sizeof(OpusPacketHeader), ResultInputDataTooSmall); + + auto* header_p{reinterpret_cast(input_data.data())}; + OpusPacketHeader header{ReverseHeader(*header_p)}; + + LOG_ERROR(Service_Audio, "header size 0x{:X} input data size 0x{:X} in_data size 0x{:X}", + header.size, input_data.size_bytes(), in_data.size_bytes()); + + R_UNLESS(in_data.size_bytes() >= header.size && + header.size + sizeof(OpusPacketHeader) <= input_data.size_bytes(), + ResultBufferTooSmall); + + if (!shared_memory_mapped) { + R_TRY(hardware_opus.MapMemory(shared_buffer.get(), shared_buffer_size)); + shared_memory_mapped = true; + } + + std::memcpy(in_data.data(), input_data.data() + sizeof(OpusPacketHeader), header.size); + + R_TRY(hardware_opus.DecodeInterleavedForMultiStream( + out_samples, out_data.data(), out_data.size_bytes(), channel_count, in_data.data(), + header.size, shared_buffer.get(), time_taken, reset)); + + std::memcpy(output_data.data(), out_data.data(), out_samples * channel_count * sizeof(s16)); + + *out_data_size = header.size + sizeof(OpusPacketHeader); + *out_sample_count = out_samples; + if (out_time_taken) { + *out_time_taken = time_taken / 1000; + } + R_SUCCEED(); +} + +} // namespace AudioCore::OpusDecoder diff --git a/src/audio_core/opus/decoder.h b/src/audio_core/opus/decoder.h new file mode 100644 index 000000000..d08d8a4a4 --- /dev/null +++ b/src/audio_core/opus/decoder.h @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/opus/parameters.h" +#include "common/common_types.h" +#include "core/hle/kernel/k_transfer_memory.h" +#include "core/hle/service/audio/errors.h" + +namespace Core { +class System; +} + +namespace AudioCore::OpusDecoder { +class HardwareOpus; + +class OpusDecoder { +public: + explicit OpusDecoder(Core::System& system, HardwareOpus& hardware_opus_); + ~OpusDecoder(); + + Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, + u64 transfer_memory_size); + Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, + u64 transfer_memory_size); + Result DecodeInterleaved(u32* out_data_size, u64* out_time_taken, u32* out_sample_count, + std::span input_data, std::span output_data, bool reset); + Result SetContext([[maybe_unused]] std::span context); + Result DecodeInterleavedForMultiStream(u32* out_data_size, u64* out_time_taken, + u32* out_sample_count, std::span input_data, + std::span output_data, bool reset); + +private: + Core::System& system; + HardwareOpus& hardware_opus; + std::unique_ptr shared_buffer{}; + u64 shared_buffer_size; + std::span in_data{}; + std::span out_data{}; + u64 buffer_size{}; + s32 sample_rate{}; + s32 channel_count{}; + bool use_large_frame_size{false}; + s32 total_stream_count{}; + s32 stereo_stream_count{}; + bool shared_memory_mapped{false}; + bool decode_object_initialized{false}; +}; + +} // namespace AudioCore::OpusDecoder diff --git a/src/audio_core/opus/decoder_manager.cpp b/src/audio_core/opus/decoder_manager.cpp new file mode 100644 index 000000000..4a5382973 --- /dev/null +++ b/src/audio_core/opus/decoder_manager.cpp @@ -0,0 +1,102 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/adsp/apps/opus/opus_decoder.h" +#include "audio_core/opus/decoder_manager.h" +#include "common/alignment.h" +#include "core/core.h" + +namespace AudioCore::OpusDecoder { +using namespace Service::Audio; + +namespace { +bool IsValidChannelCount(u32 channel_count) { + return channel_count == 1 || channel_count == 2; +} + +bool IsValidMultiStreamChannelCount(u32 channel_count) { + return channel_count > 0 && channel_count <= OpusStreamCountMax; +} + +bool IsValidSampleRate(u32 sample_rate) { + return sample_rate == 8'000 || sample_rate == 12'000 || sample_rate == 16'000 || + sample_rate == 24'000 || sample_rate == 48'000; +} + +bool IsValidStreamCount(u32 channel_count, u32 total_stream_count, u32 stereo_stream_count) { + return total_stream_count > 0 && stereo_stream_count > 0 && + stereo_stream_count <= total_stream_count && + total_stream_count + stereo_stream_count <= channel_count; +} + +} // namespace + +OpusDecoderManager::OpusDecoderManager(Core::System& system_) + : system{system_}, hardware_opus{system} { + for (u32 i = 0; i < MaxChannels; i++) { + required_workbuffer_sizes[i] = hardware_opus.GetWorkBufferSize(1 + i); + } +} + +Result OpusDecoderManager::GetWorkBufferSize(OpusParameters& params, u64& out_size) { + OpusParametersEx ex{ + .sample_rate = params.sample_rate, + .channel_count = params.channel_count, + .use_large_frame_size = false, + }; + R_RETURN(GetWorkBufferSizeExEx(ex, out_size)); +} + +Result OpusDecoderManager::GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size) { + R_RETURN(GetWorkBufferSizeExEx(params, out_size)); +} + +Result OpusDecoderManager::GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size) { + R_UNLESS(IsValidChannelCount(params.channel_count), ResultInvalidOpusChannelCount); + R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate); + + auto work_buffer_size{required_workbuffer_sizes[params.channel_count - 1]}; + auto frame_size{params.use_large_frame_size ? 5760 : 1920}; + work_buffer_size += + Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64); + out_size = work_buffer_size + 0x600; + R_SUCCEED(); +} + +Result OpusDecoderManager::GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params, + u64& out_size) { + OpusMultiStreamParametersEx ex{ + .sample_rate = params.sample_rate, + .channel_count = params.channel_count, + .total_stream_count = params.total_stream_count, + .stereo_stream_count = params.stereo_stream_count, + .use_large_frame_size = false, + .mappings = {}, + }; + R_RETURN(GetWorkBufferSizeForMultiStreamExEx(ex, out_size)); +} + +Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params, + u64& out_size) { + R_RETURN(GetWorkBufferSizeForMultiStreamExEx(params, out_size)); +} + +Result OpusDecoderManager::GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params, + u64& out_size) { + R_UNLESS(IsValidMultiStreamChannelCount(params.channel_count), ResultInvalidOpusChannelCount); + R_UNLESS(IsValidSampleRate(params.sample_rate), ResultInvalidOpusSampleRate); + R_UNLESS(IsValidStreamCount(params.channel_count, params.total_stream_count, + params.stereo_stream_count), + ResultInvalidOpusSampleRate); + + auto work_buffer_size{hardware_opus.GetWorkBufferSizeForMultiStream( + params.total_stream_count, params.stereo_stream_count)}; + auto frame_size{params.use_large_frame_size ? 5760 : 1920}; + work_buffer_size += Common::AlignUp(1500 * params.total_stream_count, 64); + work_buffer_size += + Common::AlignUp((frame_size * params.channel_count) / (48'000 / params.sample_rate), 64); + out_size = work_buffer_size; + R_SUCCEED(); +} + +} // namespace AudioCore::OpusDecoder diff --git a/src/audio_core/opus/decoder_manager.h b/src/audio_core/opus/decoder_manager.h new file mode 100644 index 000000000..466e1967b --- /dev/null +++ b/src/audio_core/opus/decoder_manager.h @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/opus/hardware_opus.h" +#include "audio_core/opus/parameters.h" +#include "common/common_types.h" +#include "core/hle/service/audio/errors.h" + +namespace Core { +class System; +} + +namespace AudioCore::OpusDecoder { + +class OpusDecoderManager { +public: + OpusDecoderManager(Core::System& system); + + HardwareOpus& GetHardwareOpus() { + return hardware_opus; + } + + Result GetWorkBufferSize(OpusParameters& params, u64& out_size); + Result GetWorkBufferSizeEx(OpusParametersEx& params, u64& out_size); + Result GetWorkBufferSizeExEx(OpusParametersEx& params, u64& out_size); + Result GetWorkBufferSizeForMultiStream(OpusMultiStreamParameters& params, u64& out_size); + Result GetWorkBufferSizeForMultiStreamEx(OpusMultiStreamParametersEx& params, u64& out_size); + Result GetWorkBufferSizeForMultiStreamExEx(OpusMultiStreamParametersEx& params, u64& out_size); + +private: + Core::System& system; + HardwareOpus hardware_opus; + std::array required_workbuffer_sizes{}; +}; + +} // namespace AudioCore::OpusDecoder diff --git a/src/audio_core/opus/hardware_opus.cpp b/src/audio_core/opus/hardware_opus.cpp new file mode 100644 index 000000000..d6544dcb0 --- /dev/null +++ b/src/audio_core/opus/hardware_opus.cpp @@ -0,0 +1,241 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "audio_core/audio_core.h" +#include "audio_core/opus/hardware_opus.h" +#include "core/core.h" + +namespace AudioCore::OpusDecoder { +namespace { +using namespace Service::Audio; + +static constexpr Result ResultCodeFromLibOpusErrorCode(u64 error_code) { + s32 error{static_cast(error_code)}; + ASSERT(error <= OPUS_OK); + switch (error) { + case OPUS_ALLOC_FAIL: + R_THROW(ResultLibOpusAllocFail); + case OPUS_INVALID_STATE: + R_THROW(ResultLibOpusInvalidState); + case OPUS_UNIMPLEMENTED: + R_THROW(ResultLibOpusUnimplemented); + case OPUS_INVALID_PACKET: + R_THROW(ResultLibOpusInvalidPacket); + case OPUS_INTERNAL_ERROR: + R_THROW(ResultLibOpusInternalError); + case OPUS_BUFFER_TOO_SMALL: + R_THROW(ResultBufferTooSmall); + case OPUS_BAD_ARG: + R_THROW(ResultLibOpusBadArg); + case OPUS_OK: + R_RETURN(ResultSuccess); + } + UNREACHABLE(); +} + +} // namespace + +HardwareOpus::HardwareOpus(Core::System& system_) + : system{system_}, opus_decoder{system.AudioCore().ADSP().OpusDecoder()} { + opus_decoder.SetSharedMemory(shared_memory); +} + +u64 HardwareOpus::GetWorkBufferSize(u32 channel) { + if (!opus_decoder.IsRunning()) { + return 0; + } + std::scoped_lock l{mutex}; + shared_memory.host_send_data[0] = channel; + opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::GetWorkBufferSize); + auto msg = opus_decoder.Receive(ADSP::Direction::Host); + if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeOK) { + LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", + ADSP::OpusDecoder::Message::GetWorkBufferSizeOK, msg); + return 0; + } + return shared_memory.dsp_return_data[0]; +} + +u64 HardwareOpus::GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count) { + std::scoped_lock l{mutex}; + shared_memory.host_send_data[0] = total_stream_count; + shared_memory.host_send_data[1] = stereo_stream_count; + opus_decoder.Send(ADSP::Direction::DSP, + ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStream); + auto msg = opus_decoder.Receive(ADSP::Direction::Host); + if (msg != ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK) { + LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", + ADSP::OpusDecoder::Message::GetWorkBufferSizeForMultiStreamOK, msg); + return 0; + } + return shared_memory.dsp_return_data[0]; +} + +Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer, + u64 buffer_size) { + std::scoped_lock l{mutex}; + shared_memory.host_send_data[0] = (u64)buffer; + shared_memory.host_send_data[1] = buffer_size; + shared_memory.host_send_data[2] = sample_rate; + shared_memory.host_send_data[3] = channel_count; + + opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::InitializeDecodeObject); + auto msg = opus_decoder.Receive(ADSP::Direction::Host); + if (msg != ADSP::OpusDecoder::Message::InitializeDecodeObjectOK) { + LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", + ADSP::OpusDecoder::Message::InitializeDecodeObjectOK, msg); + R_THROW(ResultInvalidOpusDSPReturnCode); + } + + R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); +} + +Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count, + u32 total_stream_count, + u32 stereo_stream_count, void* mappings, + void* buffer, u64 buffer_size) { + std::scoped_lock l{mutex}; + shared_memory.host_send_data[0] = (u64)buffer; + shared_memory.host_send_data[1] = buffer_size; + shared_memory.host_send_data[2] = sample_rate; + shared_memory.host_send_data[3] = channel_count; + shared_memory.host_send_data[4] = total_stream_count; + shared_memory.host_send_data[5] = stereo_stream_count; + + ASSERT(channel_count <= MaxChannels); + std::memcpy(shared_memory.channel_mapping.data(), mappings, channel_count * sizeof(u8)); + + opus_decoder.Send(ADSP::Direction::DSP, + ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObject); + auto msg = opus_decoder.Receive(ADSP::Direction::Host); + if (msg != ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK) { + LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", + ADSP::OpusDecoder::Message::InitializeMultiStreamDecodeObjectOK, msg); + R_THROW(ResultInvalidOpusDSPReturnCode); + } + + R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); +} + +Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) { + std::scoped_lock l{mutex}; + shared_memory.host_send_data[0] = (u64)buffer; + shared_memory.host_send_data[1] = buffer_size; + + opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::ShutdownDecodeObject); + auto msg = opus_decoder.Receive(ADSP::Direction::Host); + ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, + "Expected Opus shutdown code {}, got {}", + ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg); + + R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); +} + +Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) { + std::scoped_lock l{mutex}; + shared_memory.host_send_data[0] = (u64)buffer; + shared_memory.host_send_data[1] = buffer_size; + + opus_decoder.Send(ADSP::Direction::DSP, + ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObject); + auto msg = opus_decoder.Receive(ADSP::Direction::Host); + ASSERT_MSG(msg == ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, + "Expected Opus shutdown code {}, got {}", + ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg); + + R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0])); +} + +Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data, + u64 output_data_size, u32 channel_count, void* input_data, + u64 input_data_size, 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[2] = input_data_size; + 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::DecodeInterleaved); + auto msg = opus_decoder.Receive(ADSP::Direction::Host); + if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedOK) { + LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", + ADSP::OpusDecoder::Message::DecodeInterleavedOK, msg); + R_THROW(ResultInvalidOpusDSPReturnCode); + } + + auto error_code{static_cast(shared_memory.dsp_return_data[0])}; + if (error_code == OPUS_OK) { + out_sample_count = static_cast(shared_memory.dsp_return_data[1]); + out_time_taken = 1000 * shared_memory.dsp_return_data[2]; + } + R_RETURN(ResultCodeFromLibOpusErrorCode(error_code)); +} + +Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data, + u64 output_data_size, u32 channel_count, + void* input_data, u64 input_data_size, + 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[2] = input_data_size; + 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); + 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); + R_THROW(ResultInvalidOpusDSPReturnCode); + } + + auto error_code{static_cast(shared_memory.dsp_return_data[0])}; + if (error_code == OPUS_OK) { + out_sample_count = static_cast(shared_memory.dsp_return_data[1]); + out_time_taken = 1000 * shared_memory.dsp_return_data[2]; + } + R_RETURN(ResultCodeFromLibOpusErrorCode(error_code)); +} + +Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) { + std::scoped_lock l{mutex}; + shared_memory.host_send_data[0] = (u64)buffer; + shared_memory.host_send_data[1] = 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); + R_THROW(ResultInvalidOpusDSPReturnCode); + } + R_SUCCEED(); +} + +Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) { + std::scoped_lock l{mutex}; + shared_memory.host_send_data[0] = (u64)buffer; + shared_memory.host_send_data[1] = 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); + R_THROW(ResultInvalidOpusDSPReturnCode); + } + R_SUCCEED(); +} + +} // namespace AudioCore::OpusDecoder diff --git a/src/audio_core/opus/hardware_opus.h b/src/audio_core/opus/hardware_opus.h new file mode 100644 index 000000000..7013a6b40 --- /dev/null +++ b/src/audio_core/opus/hardware_opus.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/adsp/apps/opus/opus_decoder.h" +#include "audio_core/adsp/apps/opus/shared_memory.h" +#include "audio_core/adsp/mailbox.h" +#include "core/hle/service/audio/errors.h" + +namespace AudioCore::OpusDecoder { +class HardwareOpus { +public: + HardwareOpus(Core::System& system); + + u64 GetWorkBufferSize(u32 channel); + u64 GetWorkBufferSizeForMultiStream(u32 total_stream_count, u32 stereo_stream_count); + + Result InitializeDecodeObject(u32 sample_rate, u32 channel_count, void* buffer, + u64 buffer_size); + Result InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count, + u32 totaL_stream_count, u32 stereo_stream_count, + void* mappings, void* buffer, u64 buffer_size); + Result ShutdownDecodeObject(void* buffer, u64 buffer_size); + Result ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size); + Result DecodeInterleaved(u32& out_sample_count, void* output_data, u64 output_data_size, + u32 channel_count, void* input_data, u64 input_data_size, void* buffer, + u64& out_time_taken, bool reset); + Result DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data, + u64 output_data_size, u32 channel_count, + void* input_data, u64 input_data_size, void* buffer, + u64& out_time_taken, bool reset); + Result MapMemory(void* buffer, u64 buffer_size); + Result UnmapMemory(void* buffer, u64 buffer_size); + +private: + Core::System& system; + std::mutex mutex; + ADSP::OpusDecoder::OpusDecoder& opus_decoder; + ADSP::OpusDecoder::SharedMemory shared_memory; +}; +} // namespace AudioCore::OpusDecoder diff --git a/src/audio_core/opus/parameters.h b/src/audio_core/opus/parameters.h new file mode 100644 index 000000000..4c54b2825 --- /dev/null +++ b/src/audio_core/opus/parameters.h @@ -0,0 +1,54 @@ +// 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::OpusDecoder { +constexpr size_t OpusStreamCountMax = 255; +constexpr size_t MaxChannels = 2; + +struct OpusParameters { + /* 0x00 */ u32 sample_rate; + /* 0x04 */ u32 channel_count; +}; // size = 0x8 +static_assert(sizeof(OpusParameters) == 0x8, "OpusParameters has the wrong size!"); + +struct OpusParametersEx { + /* 0x00 */ u32 sample_rate; + /* 0x04 */ u32 channel_count; + /* 0x08 */ bool use_large_frame_size; + /* 0x09 */ INSERT_PADDING_BYTES(7); +}; // size = 0x10 +static_assert(sizeof(OpusParametersEx) == 0x10, "OpusParametersEx has the wrong size!"); + +struct OpusMultiStreamParameters { + /* 0x00 */ u32 sample_rate; + /* 0x04 */ u32 channel_count; + /* 0x08 */ u32 total_stream_count; + /* 0x0C */ u32 stereo_stream_count; + /* 0x10 */ std::array mappings; +}; // size = 0x110 +static_assert(sizeof(OpusMultiStreamParameters) == 0x110, + "OpusMultiStreamParameters has the wrong size!"); + +struct OpusMultiStreamParametersEx { + /* 0x00 */ u32 sample_rate; + /* 0x04 */ u32 channel_count; + /* 0x08 */ u32 total_stream_count; + /* 0x0C */ u32 stereo_stream_count; + /* 0x10 */ bool use_large_frame_size; + /* 0x11 */ INSERT_PADDING_BYTES(7); + /* 0x18 */ std::array mappings; +}; // size = 0x118 +static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118, + "OpusMultiStreamParametersEx has the wrong size!"); + +struct OpusPacketHeader { + /* 0x00 */ u32 size; + /* 0x04 */ u32 final_range; +}; // size = 0x8 +static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusPacketHeader has the wrong size!"); +} // namespace AudioCore::OpusDecoder -- cgit v1.2.3