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 ++ src/common/CMakeLists.txt | 5 +- src/common/bounded_threadsafe_queue.h | 4 +- src/core/CMakeLists.txt | 2 +- src/core/hle/result.h | 2 +- src/core/hle/service/audio/errors.h | 12 + src/core/hle/service/audio/hwopus.cpp | 722 ++++++++++++--------- src/core/hle/service/audio/hwopus.h | 25 +- 27 files changed, 1898 insertions(+), 411 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 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 diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 34877b461..416203c59 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -26,12 +26,11 @@ add_library(common STATIC assert.h atomic_helpers.h atomic_ops.h - detached_tasks.cpp - detached_tasks.h bit_cast.h bit_field.h bit_set.h bit_util.h + bounded_threadsafe_queue.h cityhash.cpp cityhash.h common_funcs.h @@ -41,6 +40,8 @@ add_library(common STATIC container_hash.h demangle.cpp demangle.h + detached_tasks.cpp + detached_tasks.h div_ceil.h dynamic_library.cpp dynamic_library.h diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h index bd87aa09b..b36fc1de9 100644 --- a/src/common/bounded_threadsafe_queue.h +++ b/src/common/bounded_threadsafe_queue.h @@ -45,13 +45,13 @@ public: } T PopWait() { - T t; + T t{}; Pop(t); return t; } T PopWait(std::stop_token stop_token) { - T t; + T t{}; Pop(t, stop_token); return t; } diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 30d2f7df6..b2dc71d4c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -890,7 +890,7 @@ endif() create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) -target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus renderdoc) +target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls renderdoc) if (MINGW) target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) endif() diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 92a1439eb..dd0b27f47 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -62,7 +62,7 @@ enum class ErrorModule : u32 { XCD = 108, TMP451 = 109, NIFM = 110, - Hwopus = 111, + HwOpus = 111, LSM6DS3 = 112, Bluetooth = 113, VI = 114, diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h index 3d3d3d97a..c41345f7e 100644 --- a/src/core/hle/service/audio/errors.h +++ b/src/core/hle/service/audio/errors.h @@ -20,4 +20,16 @@ constexpr Result ResultNotSupported{ErrorModule::Audio, 513}; constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; +constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7}; +constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8}; +constexpr Result ResultLibOpusInvalidState{ErrorModule::HwOpus, 6}; +constexpr Result ResultLibOpusUnimplemented{ErrorModule::HwOpus, 5}; +constexpr Result ResultLibOpusInvalidPacket{ErrorModule::HwOpus, 17}; +constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4}; +constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3}; +constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2}; +constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259}; +constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001}; +constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002}; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 1557e6088..6a7bf9416 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -1,420 +1,506 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include #include #include -#include -#include - +#include "audio_core/opus/decoder.h" +#include "audio_core/opus/parameters.h" #include "common/assert.h" #include "common/logging/log.h" #include "common/scratch_buffer.h" +#include "core/core.h" #include "core/hle/service/audio/hwopus.h" #include "core/hle/service/ipc_helpers.h" namespace Service::Audio { -namespace { -struct OpusDeleter { - void operator()(OpusMSDecoder* ptr) const { - opus_multistream_decoder_destroy(ptr); +using namespace AudioCore::OpusDecoder; + +class IHardwareOpusDecoder final : public ServiceFramework { +public: + explicit IHardwareOpusDecoder(Core::System& system_, HardwareOpus& hardware_opus) + : ServiceFramework{system_, "IHardwareOpusDecoder"}, + impl{std::make_unique(system_, hardware_opus)} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IHardwareOpusDecoder::DecodeInterleavedOld, "DecodeInterleavedOld"}, + {1, &IHardwareOpusDecoder::SetContext, "SetContext"}, + {2, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamOld, "DecodeInterleavedForMultiStreamOld"}, + {3, &IHardwareOpusDecoder::SetContextForMultiStream, "SetContextForMultiStream"}, + {4, &IHardwareOpusDecoder::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, + {5, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfOld, "DecodeInterleavedForMultiStreamWithPerfOld"}, + {6, &IHardwareOpusDecoder::DecodeInterleavedWithPerfAndResetOld, "DecodeInterleavedWithPerfAndResetOld"}, + {7, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfAndResetOld, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, + {8, &IHardwareOpusDecoder::DecodeInterleaved, "DecodeInterleaved"}, + {9, &IHardwareOpusDecoder::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"}, + }; + // clang-format on + + RegisterHandlers(functions); } -}; -using OpusDecoderPtr = std::unique_ptr; + Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, + u64 transfer_memory_size) { + return impl->Initialize(params, transfer_memory, transfer_memory_size); + } -struct OpusPacketHeader { - // Packet size in bytes. - u32_be size; - // Indicates the final range of the codec's entropy coder. - u32_be final_range; -}; -static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size"); + Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, + u64 transfer_memory_size) { + return impl->Initialize(params, transfer_memory, transfer_memory_size); + } -class OpusDecoderState { -public: - /// Describes extra behavior that may be asked of the decoding context. - enum class ExtraBehavior { - /// No extra behavior. - None, +private: + void DecodeInterleavedOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - /// Resets the decoder context back to a freshly initialized state. - ResetContext, - }; + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); - enum class PerfTime { - Disabled, - Enabled, - }; + u32 size{}; + u32 sample_count{}; + auto result = + impl->DecodeInterleaved(&size, nullptr, &sample_count, input_data, output_data, false); - explicit OpusDecoderState(OpusDecoderPtr decoder_, u32 sample_rate_, u32 channel_count_) - : decoder{std::move(decoder_)}, sample_rate{sample_rate_}, channel_count{channel_count_} {} - - // Decodes interleaved Opus packets. Optionally allows reporting time taken to - // perform the decoding, as well as any relevant extra behavior. - void DecodeInterleaved(HLERequestContext& ctx, PerfTime perf_time, - ExtraBehavior extra_behavior) { - if (perf_time == PerfTime::Disabled) { - DecodeInterleavedHelper(ctx, nullptr, extra_behavior); - } else { - u64 performance = 0; - DecodeInterleavedHelper(ctx, &performance, extra_behavior); - } + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); } -private: - void DecodeInterleavedHelper(HLERequestContext& ctx, u64* performance, - ExtraBehavior extra_behavior) { - u32 consumed = 0; - u32 sample_count = 0; - samples.resize_destructive(ctx.GetWriteBufferNumElements()); - - if (extra_behavior == ExtraBehavior::ResetContext) { - ResetDecoderContext(); - } - - if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) { - LOG_ERROR(Audio, "Failed to decode opus data"); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } - - const u32 param_size = performance != nullptr ? 6 : 4; - IPC::ResponseBuilder rb{ctx, param_size}; - rb.Push(ResultSuccess); - rb.Push(consumed); - rb.Push(sample_count); - if (performance) { - rb.Push(*performance); - } - ctx.WriteBuffer(samples); + void SetContext(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + LOG_DEBUG(Service_Audio, "called"); + + auto input_data{ctx.ReadBuffer(0)}; + auto result = impl->SetContext(input_data); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void DecodeInterleavedForMultiStreamOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, nullptr, &sample_count, + input_data, output_data, false); + + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); } - bool DecodeOpusData(u32& consumed, u32& sample_count, std::span input, - std::span output, u64* out_performance_time) const { - const auto start_time = std::chrono::steady_clock::now(); - const std::size_t raw_output_sz = output.size() * sizeof(opus_int16); - if (sizeof(OpusPacketHeader) > input.size()) { - LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}", - sizeof(OpusPacketHeader), input.size()); - return false; - } - - OpusPacketHeader hdr{}; - std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader)); - if (sizeof(OpusPacketHeader) + static_cast(hdr.size) > input.size()) { - LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}", - sizeof(OpusPacketHeader) + static_cast(hdr.size), input.size()); - return false; - } - - const auto frame = input.data() + sizeof(OpusPacketHeader); - const auto decoded_sample_count = opus_packet_get_nb_samples( - frame, static_cast(input.size() - sizeof(OpusPacketHeader)), - static_cast(sample_rate)); - if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) { - LOG_ERROR( - Audio, - "Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}", - decoded_sample_count * channel_count * sizeof(u16), raw_output_sz); - return false; - } - - const int frame_size = (static_cast(raw_output_sz / sizeof(s16) / channel_count)); - const auto out_sample_count = - opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0); - if (out_sample_count < 0) { - LOG_ERROR(Audio, - "Incorrect sample count received from opus_decode, " - "output_sample_count={}, frame_size={}, data_sz_from_hdr={}", - out_sample_count, frame_size, static_cast(hdr.size)); - return false; - } - - const auto end_time = std::chrono::steady_clock::now() - start_time; - sample_count = out_sample_count; - consumed = static_cast(sizeof(OpusPacketHeader) + hdr.size); - if (out_performance_time != nullptr) { - *out_performance_time = - std::chrono::duration_cast(end_time).count(); - } - - return true; + void SetContextForMultiStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + LOG_DEBUG(Service_Audio, "called"); + + auto input_data{ctx.ReadBuffer(0)}; + auto result = impl->SetContext(input_data); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); } - void ResetDecoderContext() { - ASSERT(decoder != nullptr); + void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, + output_data, false); + + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, + sample_count, time_taken); - opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE); + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } - OpusDecoderPtr decoder; - u32 sample_rate; - u32 channel_count; - Common::ScratchBuffer samples; -}; + void DecodeInterleavedForMultiStreamWithPerfOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; -class IHardwareOpusDecoderManager final : public ServiceFramework { -public: - explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state_) - : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{ - std::move(decoder_state_)} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"}, - {1, nullptr, "SetContext"}, - {2, nullptr, "DecodeInterleavedForMultiStreamOld"}, - {3, nullptr, "SetContextForMultiStream"}, - {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, - {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"}, - {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"}, - {7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, - {8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, - {9, &IHardwareOpusDecoderManager::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"}, - }; - // clang-format on + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); - RegisterHandlers(functions); + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, + input_data, output_data, false); + + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, + sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } -private: - void DecodeInterleavedOld(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); + void DecodeInterleavedWithPerfAndResetOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto reset{rp.Pop()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, + output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled, - OpusDecoderState::ExtraBehavior::None); + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } - void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); + void DecodeInterleavedForMultiStreamWithPerfAndResetOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto reset{rp.Pop()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, + input_data, output_data, reset); - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, - OpusDecoderState::ExtraBehavior::None); + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } void DecodeInterleaved(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); - IPC::RequestParser rp{ctx}; - const auto extra_behavior = rp.Pop() ? OpusDecoderState::ExtraBehavior::ResetContext - : OpusDecoderState::ExtraBehavior::None; - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); + auto reset{rp.Pop()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, + output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } void DecodeInterleavedForMultiStream(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); - IPC::RequestParser rp{ctx}; - const auto extra_behavior = rp.Pop() ? OpusDecoderState::ExtraBehavior::ResetContext - : OpusDecoderState::ExtraBehavior::None; - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); + auto reset{rp.Pop()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, + input_data, output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } - OpusDecoderState decoder_state; + std::unique_ptr impl; + Common::ScratchBuffer output_data; }; -std::size_t WorkerBufferSize(u32 channel_count) { - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); - constexpr int num_streams = 1; - const int num_stereo_streams = channel_count == 2 ? 1 : 0; - return opus_multistream_decoder_get_size(num_streams, num_stereo_streams); -} +void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; -// Creates the mapping table that maps the input channels to the particular -// output channels. In the stereo case, we map the left and right input channels -// to the left and right output channels respectively. -// -// However, in the monophonic case, we only map the one available channel -// to the sole output channel. We specify 255 for the would-be right channel -// as this is a special value defined by Opus to indicate to the decoder to -// ignore that channel. -std::array CreateMappingTable(u32 channel_count) { - if (channel_count == 2) { - return {{0, 1}}; - } + auto params = rp.PopRaw(); + auto transfer_memory_size{rp.Pop()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject( + transfer_memory_handle)}; + + LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, transfer_memory_size); - return {{0, 255}}; + auto decoder{std::make_shared(system, impl.GetHardwareOpus())}; + + OpusParametersEx ex{ + .sample_rate = params.sample_rate, + .channel_count = params.channel_count, + .use_large_frame_size = false, + }; + auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result); + rb.PushIpcInterface(decoder); } -} // Anonymous namespace void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto sample_rate = rp.Pop(); - const auto channel_count = rp.Pop(); + auto params = rp.PopRaw(); - LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count); + u64 size{}; + auto result = impl.GetWorkBufferSize(params, size); - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); + LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} -- returned size 0x{:X}", + params.sample_rate, params.channel_count, size); - const u32 worker_buffer_sz = static_cast(WorkerBufferSize(channel_count)); - LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(worker_buffer_sz); + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { - GetWorkBufferSize(ctx); +void HwOpus::OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParameters params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); + + auto transfer_memory_size{rp.Pop()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject( + transfer_memory_handle)}; + + LOG_DEBUG(Service_Audio, + "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " + "transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, params.total_stream_count, + params.stereo_stream_count, transfer_memory_size); + + auto decoder{std::make_shared(system, impl.GetHardwareOpus())}; + + 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{}, + }; + std::memcpy(ex.mappings.data(), params.mappings.data(), sizeof(params.mappings)); + auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result); + rb.PushIpcInterface(decoder); } -void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) { - GetWorkBufferSizeEx(ctx); +void HwOpus::GetWorkBufferSizeForMultiStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParameters params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); + + u64 size{}; + auto result = impl.GetWorkBufferSizeForMultiStream(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { - OpusMultiStreamParametersEx param; - std::memcpy(¶m, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); +void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - const auto sample_rate = param.sample_rate; - const auto channel_count = param.channel_count; - const auto number_streams = param.number_streams; - const auto number_stereo_streams = param.number_stereo_streams; + auto params = rp.PopRaw(); + auto transfer_memory_size{rp.Pop()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject( + transfer_memory_handle)}; - LOG_DEBUG( - Audio, - "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}", - sample_rate, channel_count, number_streams, number_stereo_streams); + LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, transfer_memory_size); - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); + auto decoder{std::make_shared(system, impl.GetHardwareOpus())}; - const u32 worker_buffer_sz = - static_cast(opus_multistream_decoder_get_size(number_streams, number_stereo_streams)); + auto result = + decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push(worker_buffer_sz); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result); + rb.PushIpcInterface(decoder); } -void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { +void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto sample_rate = rp.Pop(); - const auto channel_count = rp.Pop(); - const auto buffer_sz = rp.Pop(); - - LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate, - channel_count, buffer_sz); - - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); - - const std::size_t worker_sz = WorkerBufferSize(channel_count); - ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); - - const int num_stereo_streams = channel_count == 2 ? 1 : 0; - const auto mapping_table = CreateMappingTable(channel_count); - - int error = 0; - OpusDecoderPtr decoder{ - opus_multistream_decoder_create(sample_rate, static_cast(channel_count), 1, - num_stereo_streams, mapping_table.data(), &error)}; - if (error != OPUS_OK || decoder == nullptr) { - LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } + auto params = rp.PopRaw(); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface( - system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); + u64 size{}; + auto result = impl.GetWorkBufferSizeEx(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { +void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto sample_rate = rp.Pop(); - const auto channel_count = rp.Pop(); - LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParametersEx params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); + + auto transfer_memory_size{rp.Pop()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject( + transfer_memory_handle)}; - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); + LOG_DEBUG(Service_Audio, + "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " + "use_large_frame_size {}" + "transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, params.total_stream_count, + params.stereo_stream_count, params.use_large_frame_size, transfer_memory_size); - const int num_stereo_streams = channel_count == 2 ? 1 : 0; - const auto mapping_table = CreateMappingTable(channel_count); + auto decoder{std::make_shared(system, impl.GetHardwareOpus())}; - int error = 0; - OpusDecoderPtr decoder{ - opus_multistream_decoder_create(sample_rate, static_cast(channel_count), 1, - num_stereo_streams, mapping_table.data(), &error)}; - if (error != OPUS_OK || decoder == nullptr) { - LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } + auto result = + decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface( - system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); + rb.Push(result); + rb.PushIpcInterface(decoder); } -void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) { +void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; OpusMultiStreamParametersEx params; - std::memcpy(¶ms, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); - - const auto& sample_rate = params.sample_rate; - const auto& channel_count = params.channel_count; - - LOG_INFO( - Audio, - "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}", - sample_rate, channel_count, params.number_streams, params.number_stereo_streams); - - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - - int error = 0; - OpusDecoderPtr decoder{opus_multistream_decoder_create( - sample_rate, static_cast(channel_count), params.number_streams, - params.number_stereo_streams, params.channel_mappings.data(), &error)}; - if (error != OPUS_OK || decoder == nullptr) { - LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface( - system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); + u64 size{}; + auto result = impl.GetWorkBufferSizeForMultiStreamEx(params, size); + + LOG_DEBUG(Service_Audio, + "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " + "use_large_frame_size {} -- returned size 0x{:X}", + params.sample_rate, params.channel_count, params.total_stream_count, + params.stereo_stream_count, params.use_large_frame_size, size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); +} + +void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto params = rp.PopRaw(); + + u64 size{}; + auto result = impl.GetWorkBufferSizeExEx(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); +} + +void HwOpus::GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParametersEx params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); + + u64 size{}; + auto result = impl.GetWorkBufferSizeForMultiStreamExEx(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { +HwOpus::HwOpus(Core::System& system_) + : ServiceFramework{system_, "hwopus"}, system{system_}, impl{system} { static const FunctionInfo functions[] = { {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"}, {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, - {2, nullptr, "OpenOpusDecoderForMultiStream"}, - {3, nullptr, "GetWorkBufferSizeForMultiStream"}, + {2, &HwOpus::OpenHardwareOpusDecoderForMultiStream, "OpenOpusDecoderForMultiStream"}, + {3, &HwOpus::GetWorkBufferSizeForMultiStream, "GetWorkBufferSizeForMultiStream"}, {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx, "OpenHardwareOpusDecoderForMultiStreamEx"}, {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"}, - {9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"}, + {9, &HwOpus::GetWorkBufferSizeForMultiStreamExEx, "GetWorkBufferSizeForMultiStreamExEx"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h index 90867bf74..d3960065e 100644 --- a/src/core/hle/service/audio/hwopus.h +++ b/src/core/hle/service/audio/hwopus.h @@ -3,6 +3,7 @@ #pragma once +#include "audio_core/opus/decoder_manager.h" #include "core/hle/service/service.h" namespace Core { @@ -11,18 +12,6 @@ class System; namespace Service::Audio { -struct OpusMultiStreamParametersEx { - u32 sample_rate; - u32 channel_count; - u32 number_streams; - u32 number_stereo_streams; - u32 use_large_frame_size; - u32 padding; - std::array channel_mappings; -}; -static_assert(sizeof(OpusMultiStreamParametersEx) == 0x118, - "OpusMultiStreamParametersEx has incorrect size"); - class HwOpus final : public ServiceFramework { public: explicit HwOpus(Core::System& system_); @@ -30,12 +19,18 @@ public: private: void OpenHardwareOpusDecoder(HLERequestContext& ctx); - void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); - void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx); void GetWorkBufferSize(HLERequestContext& ctx); + void OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx); + void GetWorkBufferSizeForMultiStream(HLERequestContext& ctx); + void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); void GetWorkBufferSizeEx(HLERequestContext& ctx); - void GetWorkBufferSizeExEx(HLERequestContext& ctx); + void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx); void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); + void GetWorkBufferSizeExEx(HLERequestContext& ctx); + void GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx); + + Core::System& system; + AudioCore::OpusDecoder::OpusDecoderManager impl; }; } // namespace Service::Audio -- cgit v1.2.3