From d1ba4c91b2861b77fec3700976469d180c211adf Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Mon, 4 Nov 2024 22:46:59 +0300 Subject: [PATCH] refactored most of ajm --- CMakeLists.txt | 1 + src/core/libraries/ajm/ajm.cpp | 10 +- src/core/libraries/ajm/ajm.h | 14 +- src/core/libraries/ajm/ajm_at9.cpp | 178 +++++------------- src/core/libraries/ajm/ajm_at9.h | 40 ++-- src/core/libraries/ajm/ajm_batch.cpp | 22 +-- src/core/libraries/ajm/ajm_batch.h | 4 +- src/core/libraries/ajm/ajm_context.cpp | 63 +------ src/core/libraries/ajm/ajm_context.h | 3 +- src/core/libraries/ajm/ajm_instance.cpp | 101 ++++++++++ src/core/libraries/ajm/ajm_instance.h | 106 +++++++---- src/core/libraries/ajm/ajm_mp3.cpp | 233 ++++++++++++------------ src/core/libraries/ajm/ajm_mp3.h | 16 +- 13 files changed, 392 insertions(+), 399 deletions(-) create mode 100644 src/core/libraries/ajm/ajm_instance.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 718706fea..d9479e851 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -188,6 +188,7 @@ set(AJM_LIB src/core/libraries/ajm/ajm.cpp src/core/libraries/ajm/ajm_context.cpp src/core/libraries/ajm/ajm_context.h src/core/libraries/ajm/ajm_error.h + src/core/libraries/ajm/ajm_instance.cpp src/core/libraries/ajm/ajm_instance.h src/core/libraries/ajm/ajm_mp3.cpp src/core/libraries/ajm/ajm_mp3.h diff --git a/src/core/libraries/ajm/ajm.cpp b/src/core/libraries/ajm/ajm.cpp index ebab85061..859bcb066 100644 --- a/src/core/libraries/ajm/ajm.cpp +++ b/src/core/libraries/ajm/ajm.cpp @@ -72,7 +72,7 @@ int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context_id, u8* p_batch, u32 batch_s int PS4_SYSV_ABI sceAjmBatchWait(const u32 context_id, const u32 batch_id, const u32 timeout, AjmBatchError* const batch_error) { - LOG_INFO(Lib_Ajm, "called context = {}, batch_id = {}, timeout = {}", context_id, batch_id, + LOG_TRACE(Lib_Ajm, "called context = {}, batch_id = {}, timeout = {}", context_id, batch_id, timeout); return context->BatchWait(batch_id, timeout, batch_error); } @@ -84,13 +84,6 @@ int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData() { int PS4_SYSV_ABI sceAjmDecMp3ParseFrame(const u8* buf, u32 stream_size, int parse_ofl, AjmDecMp3ParseFrame* frame) { - LOG_INFO(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl); - if (buf == nullptr || stream_size < 4 || frame == nullptr) { - return ORBIS_AJM_ERROR_INVALID_PARAMETER; - } - if ((buf[0] & SYNCWORDH) != SYNCWORDH || (buf[1] & SYNCWORDL) != SYNCWORDL) { - return ORBIS_AJM_ERROR_INVALID_PARAMETER; - } return AjmMp3Decoder::ParseMp3Header(buf, stream_size, parse_ofl, frame); } @@ -101,6 +94,7 @@ int PS4_SYSV_ABI sceAjmFinalize() { int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* p_context_id) { LOG_INFO(Lib_Ajm, "called reserved = {}", reserved); + ASSERT_MSG(context == nullptr, "Multiple contexts are currently unsupported."); if (p_context_id == nullptr || reserved != 0) { return ORBIS_AJM_ERROR_INVALID_PARAMETER; } diff --git a/src/core/libraries/ajm/ajm.h b/src/core/libraries/ajm/ajm.h index ee9224a13..10e23482e 100644 --- a/src/core/libraries/ajm/ajm.h +++ b/src/core/libraries/ajm/ajm.h @@ -16,6 +16,14 @@ namespace Libraries::Ajm { constexpr u32 ORBIS_AT9_CONFIG_DATA_SIZE = 4; constexpr u32 AJM_INSTANCE_STATISTICS = 0x80000; +enum class AjmCodecType : u32 { + Mp3Dec = 0, + At9Dec = 1, + M4aacDec = 2, + Max = 23, +}; +DECLARE_ENUM_FLAG_OPERATORS(AjmCodecType); + struct AjmBatchInfo { void* pBuffer; u64 offset; @@ -113,6 +121,11 @@ struct AjmDecAt9InitializeParameters { u32 reserved; }; +union AjmSidebandInitParameters { + AjmDecAt9InitializeParameters at9; + u8 reserved[8]; +}; + union AjmInstanceFlags { u64 raw; struct { @@ -126,7 +139,6 @@ union AjmInstanceFlags { }; struct AjmDecMp3ParseFrame; -enum class AjmCodecType : u32; int PS4_SYSV_ABI sceAjmBatchCancel(const u32 context_id, const u32 batch_id); int PS4_SYSV_ABI sceAjmBatchErrorDump(); diff --git a/src/core/libraries/ajm/ajm_at9.cpp b/src/core/libraries/ajm/ajm_at9.cpp index 56795d6fc..441c0fbb4 100644 --- a/src/core/libraries/ajm/ajm_at9.cpp +++ b/src/core/libraries/ajm/ajm_at9.cpp @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include #include "common/assert.h" #include "core/libraries/ajm/ajm_at9.h" #include "error_codes.h" @@ -10,165 +8,83 @@ extern "C" { #include #include -#include } +#include + namespace Libraries::Ajm { AjmAt9Decoder::AjmAt9Decoder() { - handle = Atrac9GetHandle(); - ASSERT_MSG(handle, "Atrac9GetHandle failed"); + m_handle = Atrac9GetHandle(); + ASSERT_MSG(m_handle, "Atrac9GetHandle failed"); AjmAt9Decoder::Reset(); } AjmAt9Decoder::~AjmAt9Decoder() { - Atrac9ReleaseHandle(handle); + Atrac9ReleaseHandle(m_handle); } void AjmAt9Decoder::Reset() { - total_decoded_samples = 0; - gapless = {}; + Atrac9ReleaseHandle(m_handle); + m_handle = Atrac9GetHandle(); + Atrac9InitDecoder(m_handle, m_config_data); + Atrac9GetCodecInfo(m_handle, &m_codec_info); - ResetCodec(); -} - -void AjmAt9Decoder::ResetCodec() { - Atrac9ReleaseHandle(handle); - handle = Atrac9GetHandle(); - Atrac9InitDecoder(handle, config_data); - - Atrac9CodecInfo codec_info; - Atrac9GetCodecInfo(handle, &codec_info); - num_frames = 0; - superframe_bytes_remain = codec_info.superframeSize; - gapless.skipped_samples = 0; - gapless_decoded_samples = 0; + m_num_frames = 0; + m_superframe_bytes_remain = m_codec_info.superframeSize; } void AjmAt9Decoder::Initialize(const void* buffer, u32 buffer_size) { ASSERT_MSG(buffer_size == sizeof(AjmDecAt9InitializeParameters), "Incorrect At9 initialization buffer size {}", buffer_size); const auto params = reinterpret_cast(buffer); - std::memcpy(config_data, params->config_data, ORBIS_AT9_CONFIG_DATA_SIZE); + std::memcpy(m_config_data, params->config_data, ORBIS_AT9_CONFIG_DATA_SIZE); AjmAt9Decoder::Reset(); + m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels, 0); } -void AjmAt9Decoder::GetCodecInfo(void* out_info) { - Atrac9CodecInfo decoder_codec_info; - Atrac9GetCodecInfo(handle, &decoder_codec_info); - - auto* codec_info = reinterpret_cast(out_info); - codec_info->uiFrameSamples = decoder_codec_info.frameSamples; - codec_info->uiFramesInSuperFrame = decoder_codec_info.framesInSuperframe; - codec_info->uiNextFrameSize = static_cast(handle)->Config.FrameBytes; - codec_info->uiSuperFrameSize = decoder_codec_info.superframeSize; +void AjmAt9Decoder::GetInfo(void* out_info) { + auto* info = reinterpret_cast(out_info); + info->super_frame_size = m_codec_info.superframeSize; + info->frames_in_super_frame = m_codec_info.framesInSuperframe; + info->frame_samples = m_codec_info.frameSamples; + info->next_frame_size = static_cast(m_handle)->Config.FrameBytes; } -void AjmAt9Decoder::Decode(const AjmJob::Input* input, AjmJob::Output* output) { - Atrac9CodecInfo codec_info; - Atrac9GetCodecInfo(handle, &codec_info); +u32 AjmAt9Decoder::ProcessFrame(std::span& in_buf, SparseOutputBuffer& output, + AjmSidebandGaplessDecode& gapless, u32 max_samples_per_channel) { + int bytes_used = 0; + u32 ret = Atrac9Decode(m_handle, in_buf.data(), m_pcm_buffer.data(), &bytes_used); + ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret); + in_buf = in_buf.subspan(bytes_used); - size_t out_buffer_index = 0; - std::span in_buf(input->buffer); - std::span out_buf = output->buffers[out_buffer_index]; - const auto should_decode = [&] { - if (in_buf.empty() || out_buf.empty()) { - return false; - } - if (gapless.total_samples && gapless.total_samples < gapless_decoded_samples) { - return false; - } - return true; - }; + m_superframe_bytes_remain -= bytes_used; + std::span pcm_data{m_pcm_buffer}; - const auto write_output = [&](std::span pcm) { - while (!pcm.empty()) { - auto size = std::min(pcm.size() * sizeof(u16), out_buf.size()); - std::memcpy(out_buf.data(), pcm.data(), size); - pcm = pcm.subspan(size >> 1); - out_buf = out_buf.subspan(size); - if (out_buf.empty()) { - out_buffer_index += 1; - if (out_buffer_index >= output->buffers.size()) { - return pcm.empty(); - } - out_buf = output->buffers[out_buffer_index]; - } - } - return true; - }; - - int num_superframes = 0; - const auto pcm_frame_size = codec_info.channels * codec_info.frameSamples * sizeof(u16); - std::vector pcm_buffer(pcm_frame_size >> 1); - while (should_decode()) { - int bytes_used = 0; - u32 ret = Atrac9Decode(handle, in_buf.data(), pcm_buffer.data(), &bytes_used); - ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret); - in_buf = in_buf.subspan(bytes_used); - superframe_bytes_remain -= bytes_used; - const size_t samples_remain = - gapless.total_samples != 0 - ? (gapless.total_samples - gapless_decoded_samples) * codec_info.channels - : std::numeric_limits::max(); - bool written = false; - if (gapless.skipped_samples < gapless.skip_samples) { - gapless.skipped_samples += codec_info.frameSamples; - if (gapless.skipped_samples > gapless.skip_samples) { - const u32 nsamples = gapless.skipped_samples - gapless.skip_samples; - const auto start = codec_info.frameSamples - nsamples; - written = write_output({pcm_buffer.data() + start, nsamples * codec_info.channels}); - gapless.skipped_samples = gapless.skip_samples; - total_decoded_samples += nsamples; - if (gapless.total_samples != 0) { - gapless_decoded_samples += nsamples; - } - } - } else { - const auto pcm_size = std::min(pcm_buffer.size(), samples_remain); - const auto nsamples = pcm_size / codec_info.channels; - written = write_output({pcm_buffer.data(), pcm_size}); - total_decoded_samples += nsamples; - if (gapless.total_samples != 0) { - gapless_decoded_samples += nsamples; - } - } - - num_frames += 1; - if ((num_frames % codec_info.framesInSuperframe) == 0) { - num_frames = 0; - if (superframe_bytes_remain) { - if (output->p_stream) { - output->p_stream->input_consumed += superframe_bytes_remain; - } - in_buf = in_buf.subspan(superframe_bytes_remain); - } - superframe_bytes_remain = codec_info.superframeSize; - num_superframes += 1; - } - if (output->p_stream) { - output->p_stream->input_consumed += bytes_used; - if (written) { - output->p_stream->output_written += - std::min(pcm_frame_size, samples_remain * sizeof(16)); - } - } - if (output->p_mframe) { - output->p_mframe->num_frames += 1; - } + if (gapless.skipped_samples < gapless.skip_samples) { + const auto skipped_samples = std::min(u32(m_codec_info.frameSamples), + u32(gapless.skip_samples - gapless.skipped_samples)); + gapless.skipped_samples += skipped_samples; + pcm_data = pcm_data.subspan(skipped_samples * m_codec_info.channels); } - if (flags.gapless_loop && gapless.total_samples != 0 && - gapless_decoded_samples >= gapless.total_samples) { - ResetCodec(); + const auto max_samples = max_samples_per_channel == std::numeric_limits::max() + ? max_samples_per_channel + : max_samples_per_channel * m_codec_info.channels; + + const auto pcm_size = std::min(u32(pcm_data.size()), max_samples); + const auto written = output.Write(pcm_data.subspan(0, pcm_size)); + + m_num_frames += 1; + if ((m_num_frames % m_codec_info.framesInSuperframe) == 0) { + if (m_superframe_bytes_remain) { + in_buf = in_buf.subspan(m_superframe_bytes_remain); + } + m_superframe_bytes_remain = m_codec_info.superframeSize; + m_num_frames = 0; } - if (output->p_stream) { - output->p_stream->total_decoded_samples = total_decoded_samples; - } - - LOG_TRACE(Lib_Ajm, "Decoded buffer, in remain = {}, out remain = {}", in_buf.size(), - out_buf.size()); + return (written / m_codec_info.channels) / sizeof(s16); } } // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_at9.h b/src/core/libraries/ajm/ajm_at9.h index 47080a08a..b0999ed0d 100644 --- a/src/core/libraries/ajm/ajm_at9.h +++ b/src/core/libraries/ajm/ajm_at9.h @@ -3,49 +3,39 @@ #pragma once -#include -#include -#include "common/assert.h" #include "common/types.h" #include "core/libraries/ajm/ajm_instance.h" -extern "C" { -#include -} +#include "libatrac9.h" namespace Libraries::Ajm { constexpr s32 ORBIS_AJM_DEC_AT9_MAX_CHANNELS = 8; struct AjmSidebandDecAt9CodecInfo { - u32 uiSuperFrameSize; - u32 uiFramesInSuperFrame; - u32 uiNextFrameSize; - u32 uiFrameSamples; + u32 super_frame_size; + u32 frames_in_super_frame; + u32 next_frame_size; + u32 frame_samples; }; -struct AjmAt9Decoder final : AjmInstance { - void* handle{}; - u8 config_data[ORBIS_AT9_CONFIG_DATA_SIZE]{}; - u32 superframe_bytes_remain{}; - u32 num_frames{}; - +struct AjmAt9Decoder final : AjmCodec { explicit AjmAt9Decoder(); ~AjmAt9Decoder() override; void Reset() override; - void Initialize(const void* buffer, u32 buffer_size) override; - - void GetCodecInfo(void* out_info) override; - u32 GetCodecInfoSize() override { - return sizeof(AjmSidebandDecAt9CodecInfo); - } - - void Decode(const AjmJob::Input* input, AjmJob::Output* output) override; + void GetInfo(void* out_info) override; + u32 ProcessFrame(std::span& input, SparseOutputBuffer& output, + AjmSidebandGaplessDecode& gapless, u32 max_samples) override; private: - void ResetCodec(); + void* m_handle{}; + u8 m_config_data[ORBIS_AT9_CONFIG_DATA_SIZE]{}; + u32 m_superframe_bytes_remain{}; + u32 m_num_frames{}; + Atrac9CodecInfo m_codec_info{}; + std::vector m_pcm_buffer; }; } // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_batch.cpp b/src/core/libraries/ajm/ajm_batch.cpp index 3a47b584d..5c76beae8 100644 --- a/src/core/libraries/ajm/ajm_batch.cpp +++ b/src/core/libraries/ajm/ajm_batch.cpp @@ -46,18 +46,18 @@ static_assert(sizeof(AjmChunkBuffer) == 16); class AjmBatchBuffer { public: - static constexpr size_t DynamicExtent = std::numeric_limits::max(); + static constexpr size_t s_dynamic_extent = std::numeric_limits::max(); AjmBatchBuffer(u8* begin, u8* end) : m_p_begin(begin), m_p_current(begin), m_size(end - begin) {} - AjmBatchBuffer(u8* begin, size_t size = DynamicExtent) + AjmBatchBuffer(u8* begin, size_t size = s_dynamic_extent) : m_p_begin(begin), m_p_current(m_p_begin), m_size(size) {} AjmBatchBuffer(std::span data) : m_p_begin(data.data()), m_p_current(m_p_begin), m_size(data.size()) {} - AjmBatchBuffer SubBuffer(size_t size = DynamicExtent) { + AjmBatchBuffer SubBuffer(size_t size = s_dynamic_extent) { auto current = m_p_current; - if (size != DynamicExtent) { + if (size != s_dynamic_extent) { m_p_current += size; } return AjmBatchBuffer(current, size); @@ -65,7 +65,8 @@ public: template T& Peek() const { - DEBUG_ASSERT(m_size == DynamicExtent || (m_p_current + sizeof(T)) <= (m_p_begin + m_size)); + DEBUG_ASSERT(m_size == s_dynamic_extent || + (m_p_current + sizeof(T)) <= (m_p_begin + m_size)); return *reinterpret_cast(m_p_current); } @@ -73,7 +74,7 @@ public: T& Consume() { auto* const result = reinterpret_cast(m_p_current); m_p_current += sizeof(T); - DEBUG_ASSERT(m_size == DynamicExtent || m_p_current <= (m_p_begin + m_size)); + DEBUG_ASSERT(m_size == s_dynamic_extent || m_p_current <= (m_p_begin + m_size)); return *result; } @@ -84,11 +85,11 @@ public: void Advance(size_t size) { m_p_current += size; - DEBUG_ASSERT(m_size == DynamicExtent || m_p_current <= (m_p_begin + m_size)); + DEBUG_ASSERT(m_size == s_dynamic_extent || m_p_current <= (m_p_begin + m_size)); } bool IsEmpty() { - return m_size != DynamicExtent && m_p_current >= (m_p_begin + m_size); + return m_size != s_dynamic_extent && m_p_current >= (m_p_begin + m_size); } size_t BytesConsumed() const { @@ -96,8 +97,8 @@ public: } size_t BytesRemaining() const { - if (m_size == DynamicExtent) { - return DynamicExtent; + if (m_size == s_dynamic_extent) { + return s_dynamic_extent; } return m_size - (m_p_current - m_p_begin); } @@ -221,7 +222,6 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) { *job.output.p_stream = AjmSidebandStream{}; } if (True(sideband_flags & AjmJobSidebandFlags::Format) && !output_batch.IsEmpty()) { - LOG_ERROR(Lib_Ajm, "SIDEBAND_FORMAT is not implemented"); job.output.p_format = &output_batch.Consume(); *job.output.p_format = AjmSidebandFormat{}; } diff --git a/src/core/libraries/ajm/ajm_batch.h b/src/core/libraries/ajm/ajm_batch.h index e7dc4320d..65110ee73 100644 --- a/src/core/libraries/ajm/ajm_batch.h +++ b/src/core/libraries/ajm/ajm_batch.h @@ -28,7 +28,7 @@ struct AjmJob { }; struct Output { - boost::container::small_vector, 4> buffers; + boost::container::small_vector, 8> buffers; AjmSidebandResult* p_result = nullptr; AjmSidebandStream* p_stream = nullptr; AjmSidebandFormat* p_format = nullptr; @@ -48,7 +48,7 @@ struct AjmBatch { std::atomic_bool waiting{}; std::atomic_bool canceled{}; std::binary_semaphore finished{0}; - boost::container::small_vector jobs; + boost::container::small_vector jobs; static std::shared_ptr FromBatchBuffer(std::span buffer); }; diff --git a/src/core/libraries/ajm/ajm_context.cpp b/src/core/libraries/ajm/ajm_context.cpp index 898ef3322..d831ab484 100644 --- a/src/core/libraries/ajm/ajm_context.cpp +++ b/src/core/libraries/ajm/ajm_context.cpp @@ -3,6 +3,7 @@ #include "common/assert.h" #include "common/logging/log.h" +#include "core/libraries/ajm/ajm.h" #include "core/libraries/ajm/ajm_at9.h" #include "core/libraries/ajm/ajm_context.h" #include "core/libraries/ajm/ajm_error.h" @@ -64,7 +65,7 @@ void AjmContext::WorkerThread(std::stop_token stop) { void AjmContext::ProcessBatch(u32 id, std::span jobs) { // Perform operation requested by control flags. for (auto& job : jobs) { - LOG_DEBUG(Lib_Ajm, "Processing job {} for instance {}. flags = {:#x}", id, job.instance_id, + LOG_TRACE(Lib_Ajm, "Processing job {} for instance {}. flags = {:#x}", id, job.instance_id, job.flags.raw); std::shared_ptr instance; @@ -75,48 +76,7 @@ void AjmContext::ProcessBatch(u32 id, std::span jobs) { instance = *p_instance; } - const auto control_flags = job.flags.control_flags; - if (True(control_flags & AjmJobControlFlags::Reset)) { - LOG_INFO(Lib_Ajm, "Resetting instance {}", job.instance_id); - instance->Reset(); - } - if (True(control_flags & AjmJobControlFlags::Initialize)) { - LOG_INFO(Lib_Ajm, "Initializing instance {}", job.instance_id); - ASSERT_MSG(job.input.init_params.has_value(), - "Initialize called without control buffer"); - auto& params = job.input.init_params.value(); - instance->Initialize(¶ms, sizeof(params)); - } - if (True(control_flags & AjmJobControlFlags::Resample)) { - LOG_ERROR(Lib_Ajm, "Unimplemented: resample params"); - ASSERT_MSG(job.input.resample_parameters.has_value(), "Resample paramters are absent"); - instance->resample_parameters = job.input.resample_parameters.value(); - } - - const auto sideband_flags = job.flags.sideband_flags; - if (True(sideband_flags & AjmJobSidebandFlags::Format)) { - ASSERT_MSG(job.input.format.has_value(), "Format parameters are absent"); - instance->format = job.input.format.value(); - } - if (True(sideband_flags & AjmJobSidebandFlags::GaplessDecode)) { - ASSERT_MSG(job.input.gapless_decode.has_value(), - "Gapless decode parameters are absent"); - auto& params = job.input.gapless_decode.value(); - instance->gapless.total_samples = params.total_samples; - instance->gapless.skip_samples = params.skip_samples; - } - - if (!job.input.buffer.empty()) { - instance->Decode(&job.input, &job.output); - } - - if (job.output.p_gapless_decode != nullptr) { - *job.output.p_gapless_decode = instance->gapless; - } - - if (job.output.p_codec_info != nullptr) { - instance->GetCodecInfo(job.output.p_codec_info); - } + instance->ExecuteJob(job); } } @@ -190,25 +150,10 @@ s32 AjmContext::InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags, return ORBIS_AJM_ERROR_CODEC_NOT_REGISTERED; } ASSERT_MSG(flags.format == 0, "Only signed 16-bit PCM output is supported currently!"); - std::unique_ptr instance; - switch (codec_type) { - case AjmCodecType::Mp3Dec: - instance = std::make_unique(); - break; - case AjmCodecType::At9Dec: - instance = std::make_unique(); - break; - default: - UNREACHABLE_MSG("Codec #{} not implemented", u32(codec_type)); - } - instance->codec_type = codec_type; - instance->num_channels = flags.channels; - instance->flags = flags; - std::optional opt_index; { std::unique_lock lock(instances_mutex); - opt_index = instances.Create(std::move(instance)); + opt_index = instances.Create(std::move(std::make_unique(codec_type, flags))); } if (!opt_index.has_value()) { return ORBIS_AJM_ERROR_OUT_OF_RESOURCES; diff --git a/src/core/libraries/ajm/ajm_context.h b/src/core/libraries/ajm/ajm_context.h index 08543dc43..e51ea4fcf 100644 --- a/src/core/libraries/ajm/ajm_context.h +++ b/src/core/libraries/ajm/ajm_context.h @@ -15,6 +15,7 @@ #include #include #include +#include namespace Libraries::Ajm { @@ -37,7 +38,7 @@ public: private: static constexpr u32 MaxInstances = 0x2fff; static constexpr u32 MaxBatches = 0x0400; - static constexpr u32 NumAjmCodecs = u32(AjmCodecType::Max); + static constexpr u32 NumAjmCodecs = std::to_underlying(AjmCodecType::Max); [[nodiscard]] bool IsRegistered(AjmCodecType type) const; diff --git a/src/core/libraries/ajm/ajm_instance.cpp b/src/core/libraries/ajm/ajm_instance.cpp new file mode 100644 index 000000000..89fcd2d9f --- /dev/null +++ b/src/core/libraries/ajm/ajm_instance.cpp @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/libraries/ajm/ajm_at9.h" +#include "core/libraries/ajm/ajm_instance.h" +#include "core/libraries/ajm/ajm_mp3.h" + +#include + +namespace Libraries::Ajm { + +AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) { + switch (codec_type) { + case AjmCodecType::At9Dec: { + m_codec = std::make_unique(); + break; + } + case AjmCodecType::Mp3Dec: { + m_codec = std::make_unique(); + break; + } + default: + UNREACHABLE_MSG("Unimplemented codec type {}", magic_enum::enum_name(codec_type)); + } +} + +void AjmInstance::ExecuteJob(AjmJob& job) { + const auto control_flags = job.flags.control_flags; + if (True(control_flags & AjmJobControlFlags::Reset)) { + LOG_TRACE(Lib_Ajm, "Resetting instance {}", job.instance_id); + m_format = {}; + m_gapless = {}; + m_resample_parameters = {}; + m_gapless_samples = 0; + m_total_samples = 0; + m_codec->Reset(); + } + if (job.input.init_params.has_value()) { + LOG_TRACE(Lib_Ajm, "Initializing instance {}", job.instance_id); + auto& params = job.input.init_params.value(); + m_codec->Initialize(¶ms, sizeof(params)); + } + if (job.input.resample_parameters.has_value()) { + UNREACHABLE_MSG("Unimplemented: resample parameters"); + m_resample_parameters = job.input.resample_parameters.value(); + } + if (job.input.format.has_value()) { + UNREACHABLE_MSG("Unimplemented: format parameters"); + m_format = job.input.format.value(); + } + if (job.input.gapless_decode.has_value()) { + auto& params = job.input.gapless_decode.value(); + m_gapless.total_samples = params.total_samples; + m_gapless.skip_samples = params.skip_samples; + } + + if (!job.input.buffer.empty() && !job.output.buffers.empty()) { + u32 frames_decoded = 0; + std::span in_buf(job.input.buffer); + SparseOutputBuffer out_buf(job.output.buffers); + + auto in_size = in_buf.size(); + auto out_size = out_buf.Size(); + while (!in_buf.empty() && !out_buf.IsEmpty() && !IsGaplessEnd()) { + const u32 samples_remain = m_gapless.total_samples != 0 + ? m_gapless.total_samples - m_gapless_samples + : std::numeric_limits::max(); + const auto nsamples = m_codec->ProcessFrame(in_buf, out_buf, m_gapless, samples_remain); + ++frames_decoded; + m_total_samples += nsamples; + m_gapless_samples += nsamples; + } + if (job.output.p_mframe) { + job.output.p_mframe->num_frames = frames_decoded; + } + if (job.output.p_stream) { + job.output.p_stream->input_consumed = in_size - in_buf.size(); + job.output.p_stream->output_written = out_size - out_buf.Size(); + job.output.p_stream->total_decoded_samples = m_total_samples; + } + } + + if (m_flags.gapless_loop && m_gapless.total_samples != 0 && + m_gapless_samples >= m_gapless.total_samples) { + m_gapless_samples = 0; + m_gapless.skipped_samples = 0; + m_codec->Reset(); + } + if (job.output.p_gapless_decode != nullptr) { + *job.output.p_gapless_decode = m_gapless; + } + if (job.output.p_codec_info != nullptr) { + m_codec->GetInfo(job.output.p_codec_info); + } +} + +bool AjmInstance::IsGaplessEnd() { + return m_gapless.total_samples != 0 && m_gapless_samples >= m_gapless.total_samples; +} + +} // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_instance.h b/src/core/libraries/ajm/ajm_instance.h index a3db567b8..d8e9a6430 100644 --- a/src/core/libraries/ajm/ajm_instance.h +++ b/src/core/libraries/ajm/ajm_instance.h @@ -8,17 +8,8 @@ #include "core/libraries/ajm/ajm.h" #include "core/libraries/ajm/ajm_batch.h" -#include - +#include #include -#include -#include - -extern "C" { -struct AVCodec; -struct AVCodecContext; -struct AVCodecParserContext; -} namespace Libraries::Ajm { @@ -35,42 +26,81 @@ constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200; constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000; constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000; -enum class AjmCodecType : u32 { - Mp3Dec = 0, - At9Dec = 1, - M4aacDec = 2, - Max = 23, -}; -DECLARE_ENUM_FLAG_OPERATORS(AjmCodecType); +class SparseOutputBuffer { +public: + SparseOutputBuffer(std::span> chunks) + : m_chunks(chunks), m_current(m_chunks.begin()) {} -union AjmSidebandInitParameters { - AjmDecAt9InitializeParameters at9; - u8 reserved[8]; + template + size_t Write(std::span pcm) { + size_t bytes_written = 0; + while (!pcm.empty() && !IsEmpty()) { + auto size = std::min(pcm.size() * sizeof(T), m_current->size()); + std::memcpy(m_current->data(), pcm.data(), size); + bytes_written += size; + pcm = pcm.subspan(size / sizeof(T)); + *m_current = m_current->subspan(size); + if (m_current->empty()) { + ++m_current; + } + } + return bytes_written; + } + + bool IsEmpty() { + return m_current == m_chunks.end(); + } + + size_t Size() { + size_t result = 0; + for (auto it = m_current; it != m_chunks.end(); ++it) { + result += it->size(); + } + return result; + } + +private: + std::span> m_chunks; + std::span>::iterator m_current; }; -struct AjmInstance { - AjmCodecType codec_type; - AjmFormatEncoding fmt{}; - AjmInstanceFlags flags{.raw = 0}; - u32 num_channels{}; - u32 index{}; - u32 gapless_decoded_samples{}; - u32 total_decoded_samples{}; - AjmSidebandFormat format{}; - AjmSidebandGaplessDecode gapless{}; - AjmSidebandResampleParameters resample_parameters{}; +struct DecodeResult { + u32 bytes_consumed{}; + u32 bytes_written{}; +}; - explicit AjmInstance() = default; - virtual ~AjmInstance() = default; - - virtual void Reset() = 0; +class AjmCodec { +public: + virtual ~AjmCodec() = default; virtual void Initialize(const void* buffer, u32 buffer_size) = 0; + virtual void Reset() = 0; + virtual void GetInfo(void* out_info) = 0; + virtual u32 ProcessFrame(std::span& input, SparseOutputBuffer& output, + AjmSidebandGaplessDecode& gapless, u32 max_samples) = 0; +}; - virtual void GetCodecInfo(void* out_info) = 0; - virtual u32 GetCodecInfoSize() = 0; +class AjmInstance { +public: + AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags); - virtual void Decode(const AjmJob::Input* input, AjmJob::Output* output) = 0; + void ExecuteJob(AjmJob& job); + +private: + bool IsGaplessEnd(); + + AjmInstanceFlags m_flags{}; + AjmSidebandFormat m_format{}; + AjmSidebandGaplessDecode m_gapless{}; + AjmSidebandResampleParameters m_resample_parameters{}; + + u32 m_gapless_samples{}; + u32 m_total_samples{}; + + std::unique_ptr m_codec; + + // AjmCodecType codec_type; + // u32 index{}; }; } // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_mp3.cpp b/src/core/libraries/ajm/ajm_mp3.cpp index 668077490..90a2c8232 100644 --- a/src/core/libraries/ajm/ajm_mp3.cpp +++ b/src/core/libraries/ajm/ajm_mp3.cpp @@ -2,7 +2,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" +#include "core/libraries/ajm/ajm_error.h" #include "core/libraries/ajm/ajm_mp3.h" +#include "core/libraries/error_codes.h" extern "C" { #include @@ -60,130 +62,140 @@ AjmMp3Decoder::AjmMp3Decoder() { } AjmMp3Decoder::~AjmMp3Decoder() { - avcodec_free_context(&c); - av_free(c); + avcodec_free_context(&codec_context); + av_free(codec_context); } void AjmMp3Decoder::Reset() { - if (c) { - avcodec_free_context(&c); - av_free(c); + if (codec_context) { + avcodec_free_context(&codec_context); + av_free(codec_context); } - c = avcodec_alloc_context3(codec); - ASSERT_MSG(c, "Could not allocate audio codec context"); - int ret = avcodec_open2(c, codec, nullptr); + codec_context = avcodec_alloc_context3(codec); + ASSERT_MSG(codec_context, "Could not allocate audio codec context"); + int ret = avcodec_open2(codec_context, codec, nullptr); ASSERT_MSG(ret >= 0, "Could not open codec"); - total_decoded_samples = 0; - gapless_decoded_samples = 0; + // total_decoded_samples = 0; + // gapless_decoded_samples = 0; } -void AjmMp3Decoder::Decode(const AjmJob::Input* input, AjmJob::Output* output) { - AVPacket* pkt = av_packet_alloc(); +u32 AjmMp3Decoder::ProcessFrame(std::span& input, SparseOutputBuffer& output, + AjmSidebandGaplessDecode& gapless, u32 max_samples) { + // AVPacket* pkt = av_packet_alloc(); - size_t out_buffer_index = 0; - std::span in_buf(input->buffer); - std::span out_buf = output->buffers[out_buffer_index]; - const auto should_decode = [&] { - if (in_buf.empty() || out_buf.empty()) { - return false; - } - if (gapless.total_samples != 0 && gapless.total_samples < gapless_decoded_samples) { - return false; - } - return true; - }; + // size_t out_buffer_index = 0; + // std::span in_buf(input->buffer); + // std::span out_buf = output->buffers[out_buffer_index]; + // const auto should_decode = [&] { + // if (in_buf.empty() || out_buf.empty()) { + // return false; + // } + // if (gapless.total_samples != 0 && gapless.total_samples < gapless_decoded_samples) { + // return false; + // } + // return true; + // }; - const auto write_output = [&](std::span pcm) { - while (!pcm.empty()) { - auto size = std::min(pcm.size() * sizeof(u16), out_buf.size()); - std::memcpy(out_buf.data(), pcm.data(), size); - pcm = pcm.subspan(size >> 1); - out_buf = out_buf.subspan(size); - if (out_buf.empty()) { - out_buffer_index += 1; - if (out_buffer_index >= output->buffers.size()) { - return pcm.empty(); - } - out_buf = output->buffers[out_buffer_index]; - } - } - return true; - }; + // const auto write_output = [&](std::span pcm) { + // while (!pcm.empty()) { + // auto size = std::min(pcm.size() * sizeof(u16), out_buf.size()); + // std::memcpy(out_buf.data(), pcm.data(), size); + // pcm = pcm.subspan(size >> 1); + // out_buf = out_buf.subspan(size); + // if (out_buf.empty()) { + // out_buffer_index += 1; + // if (out_buffer_index >= output->buffers.size()) { + // return pcm.empty(); + // } + // out_buf = output->buffers[out_buffer_index]; + // } + // } + // return true; + // }; - while (should_decode()) { - int ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size, in_buf.data(), in_buf.size(), - AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); - ASSERT_MSG(ret >= 0, "Error while parsing {}", ret); - in_buf = in_buf.subspan(ret); + // while (should_decode()) { + // int ret = av_parser_parse2(parser, codec_context, &pkt->data, &pkt->size, in_buf.data(), + // in_buf.size(), AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); + // ASSERT_MSG(ret >= 0, "Error while parsing {}", ret); + // in_buf = in_buf.subspan(ret); - if (output->p_stream) { - output->p_stream->input_consumed += ret; - } - if (pkt->size) { - // Send the packet with the compressed data to the decoder - pkt->pts = parser->pts; - pkt->dts = parser->dts; - pkt->flags = (parser->key_frame == 1) ? AV_PKT_FLAG_KEY : 0; - ret = avcodec_send_packet(c, pkt); - ASSERT_MSG(ret >= 0, "Error submitting the packet to the decoder {}", ret); + // if (output->p_stream) { + // output->p_stream->input_consumed += ret; + // } + // if (pkt->size) { + // // Send the packet with the compressed data to the decoder + // pkt->pts = parser->pts; + // pkt->dts = parser->dts; + // pkt->flags = (parser->key_frame == 1) ? AV_PKT_FLAG_KEY : 0; + // ret = avcodec_send_packet(codec_context, pkt); + // ASSERT_MSG(ret >= 0, "Error submitting the packet to the decoder {}", ret); - // Read all the output frames (in general there may be any number of them - while (ret >= 0) { - AVFrame* frame = av_frame_alloc(); - ret = avcodec_receive_frame(c, frame); - if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { - break; - } else if (ret < 0) { - UNREACHABLE_MSG("Error during decoding"); - } - if (frame->format != AV_SAMPLE_FMT_S16) { - frame = ConvertAudioFrame(frame); - } - const auto size = frame->ch_layout.nb_channels * frame->nb_samples * sizeof(u16); - if (gapless.skipped_samples < gapless.skip_samples) { - gapless.skipped_samples += frame->nb_samples; - if (gapless.skipped_samples > gapless.skip_samples) { - const u32 nsamples = gapless.skipped_samples - gapless.skip_samples; - const auto start = frame->nb_samples - nsamples; - write_output({reinterpret_cast(frame->data[0]), - nsamples * frame->ch_layout.nb_channels}); - gapless.skipped_samples = gapless.skip_samples; - total_decoded_samples += nsamples; - if (gapless.total_samples != 0) { - gapless_decoded_samples += nsamples; - } - } - } else { - write_output({reinterpret_cast(frame->data[0]), size >> 1}); - total_decoded_samples += frame->nb_samples; - if (gapless.total_samples != 0) { - gapless_decoded_samples += frame->nb_samples; - } - } - av_frame_free(&frame); - if (output->p_stream) { - output->p_stream->output_written += size; - } - if (output->p_mframe) { - output->p_mframe->num_frames += 1; - } - } - } - } - av_packet_free(&pkt); - if (gapless.total_samples != 0 && gapless_decoded_samples >= gapless.total_samples) { - if (flags.gapless_loop) { - gapless.skipped_samples = 0; - gapless_decoded_samples = 0; - } - } - if (output->p_stream) { - output->p_stream->total_decoded_samples = total_decoded_samples; - } + // // Read all the output frames (in general there may be any number of them + // while (ret >= 0) { + // AVFrame* frame = av_frame_alloc(); + // ret = avcodec_receive_frame(codec_context, frame); + // if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + // break; + // } else if (ret < 0) { + // UNREACHABLE_MSG("Error during decoding"); + // } + // if (frame->format != AV_SAMPLE_FMT_S16) { + // frame = ConvertAudioFrame(frame); + // } + // const auto size = frame->ch_layout.nb_channels * frame->nb_samples * sizeof(u16); + // if (gapless.skipped_samples < gapless.skip_samples) { + // gapless.skipped_samples += frame->nb_samples; + // if (gapless.skipped_samples > gapless.skip_samples) { + // const u32 nsamples = gapless.skipped_samples - gapless.skip_samples; + // const auto start = frame->nb_samples - nsamples; + // write_output({reinterpret_cast(frame->data[0]), + // nsamples * frame->ch_layout.nb_channels}); + // gapless.skipped_samples = gapless.skip_samples; + // total_decoded_samples += nsamples; + // if (gapless.total_samples != 0) { + // gapless_decoded_samples += nsamples; + // } + // } + // } else { + // write_output({reinterpret_cast(frame->data[0]), size >> 1}); + // total_decoded_samples += frame->nb_samples; + // if (gapless.total_samples != 0) { + // gapless_decoded_samples += frame->nb_samples; + // } + // } + // av_frame_free(&frame); + // if (output->p_stream) { + // output->p_stream->output_written += size; + // } + // if (output->p_mframe) { + // output->p_mframe->num_frames += 1; + // } + // } + // } + // } + // av_packet_free(&pkt); + // if (gapless.total_samples != 0 && gapless_decoded_samples >= gapless.total_samples) { + // if (flags.gapless_loop) { + // gapless.skipped_samples = 0; + // gapless_decoded_samples = 0; + // } + // } + // if (output->p_stream) { + // output->p_stream->total_decoded_samples = total_decoded_samples; + // } + return 0; } int AjmMp3Decoder::ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl, AjmDecMp3ParseFrame* frame) { + LOG_INFO(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl); + if (buf == nullptr || stream_size < 4 || frame == nullptr) { + return ORBIS_AJM_ERROR_INVALID_PARAMETER; + } + if ((buf[0] & SYNCWORDH) != SYNCWORDH || (buf[1] & SYNCWORDL) != SYNCWORDL) { + return ORBIS_AJM_ERROR_INVALID_PARAMETER; + } + const u32 unk_idx = buf[1] >> 3 & 1; const s32 version_idx = (buf[1] >> 3 & 3) ^ 2; const s32 sr_idx = buf[2] >> 2 & 3; @@ -196,11 +208,8 @@ int AjmMp3Decoder::ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl, frame->frame_size = (UnkTable[unk_idx] * frame->bitrate) / frame->sample_rate + padding_bit; frame->samples_per_channel = UnkTable[unk_idx] * 8; frame->encoder_delay = 0; - if (parse_ofl == 0) { - return 0; - } - return 0; + return ORBIS_OK; } } // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_mp3.h b/src/core/libraries/ajm/ajm_mp3.h index ac0c62fa8..2874f9077 100644 --- a/src/core/libraries/ajm/ajm_mp3.h +++ b/src/core/libraries/ajm/ajm_mp3.h @@ -56,25 +56,19 @@ struct AjmDecMp3GetCodecInfoResult { AjmSidebandDecMp3CodecInfo codec_info; }; -struct AjmMp3Decoder : public AjmInstance { +struct AjmMp3Decoder : public AjmCodec { const AVCodec* codec = nullptr; - AVCodecContext* c = nullptr; + AVCodecContext* codec_context = nullptr; AVCodecParserContext* parser = nullptr; - std::ofstream file; explicit AjmMp3Decoder(); ~AjmMp3Decoder() override; void Reset() override; - void Initialize(const void* buffer, u32 buffer_size) override {} - - void GetCodecInfo(void* out_info) override {} - u32 GetCodecInfoSize() override { - return sizeof(AjmSidebandDecMp3CodecInfo); - } - - void Decode(const AjmJob::Input* input, AjmJob::Output* output) override; + void GetInfo(void* out_info) override {} + u32 ProcessFrame(std::span& input, SparseOutputBuffer& output, + AjmSidebandGaplessDecode& gapless, u32 max_samples) override; static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl, AjmDecMp3ParseFrame* frame);