From e19e471158093b7b1517093a7092af1a29bf4bf4 Mon Sep 17 00:00:00 2001 From: IndecisiveTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Fri, 13 Sep 2024 17:22:51 +0300 Subject: [PATCH] ajm: Some work --- .gitmodules | 3 + externals/LibAtrac9 | 1 + src/core/libraries/ajm/ajm.cpp | 286 ++++++++++++++++++++++++++--- src/core/libraries/ajm/ajm.h | 154 +++++++++++++++- src/core/libraries/ajm/ajm_mp3.cpp | 101 ++++++++++ src/core/libraries/ajm/ajm_mp3.h | 82 +++++++++ 6 files changed, 595 insertions(+), 32 deletions(-) create mode 160000 externals/LibAtrac9 create mode 100644 src/core/libraries/ajm/ajm_mp3.cpp create mode 100644 src/core/libraries/ajm/ajm_mp3.h diff --git a/.gitmodules b/.gitmodules index be4c1851a..1bef08d4b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -90,3 +90,6 @@ url = https://github.com/shadps4-emu/ext-imgui.git shallow = true branch = docking +[submodule "externals/LibAtrac9"] + path = externals/LibAtrac9 + url = https://github.com/Vita3K/LibAtrac9 diff --git a/externals/LibAtrac9 b/externals/LibAtrac9 new file mode 160000 index 000000000..82767fe38 --- /dev/null +++ b/externals/LibAtrac9 @@ -0,0 +1 @@ +Subproject commit 82767fe38823c32536726ea798f392b0b49e66b9 diff --git a/src/core/libraries/ajm/ajm.cpp b/src/core/libraries/ajm/ajm.cpp index 441a07f63..45bfd0dac 100644 --- a/src/core/libraries/ajm/ajm.cpp +++ b/src/core/libraries/ajm/ajm.cpp @@ -1,15 +1,60 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#pragma clang optimize off +#include +#include -#include "ajm.h" -#include "ajm_error.h" - +#include "common/assert.h" #include "common/logging/log.h" +#include "core/libraries/ajm/ajm.h" +#include "core/libraries/ajm/ajm_error.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" +extern "C" { +#include +#include +#include +#include +#include +#include +} + namespace Libraries::Ajm { +static constexpr u32 AJM_INSTANCE_STATISTICS = 0x80000; + +static constexpr u32 MaxInstances = 0x2fff; + +struct AjmInstance { + AjmCodecType codec_type; +}; + +struct AjmDevice { + u32 max_prio; + u32 min_prio; + u32 curr_cursor{}; + u32 release_cursor{MaxInstances - 1}; + std::array is_registered{}; + std::array free_instances{}; + std::array instances{}; + MP3Decoder mp3dec; + + bool IsRegistered(AjmCodecType type) const { + return is_registered[static_cast(type)]; + } + + void Register(AjmCodecType type) { + is_registered[static_cast(type)] = true; + } + + AjmDevice() { + std::iota(free_instances.begin(), free_instances.end(), 1); + } +}; + +static std::unique_ptr dev{}; + int PS4_SYSV_ABI sceAjmBatchCancel() { LOG_ERROR(Lib_Ajm, "(STUBBED) called"); return ORBIS_OK; @@ -20,9 +65,48 @@ int PS4_SYSV_ABI sceAjmBatchErrorDump() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAjmBatchJobControlBufferRa() { - LOG_ERROR(Lib_Ajm, "(STUBBED) called"); - return ORBIS_OK; +void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(AjmSingleJob* batch_pos, u32 instance, AjmFlags flags, + const u8* in_buffer, u32 in_size, u8* out_buffer, + u32 out_size, const void* ret_addr) { + LOG_INFO(Lib_Ajm, "called instance = {:#x}, flags = {:#x}, cmd = {}, in_size = {:#x}, out_size = {:#x}, ret_addr = {}", + instance, flags.raw, magic_enum::enum_name(AjmJobControlFlags(flags.command)), + in_size, out_size, fmt::ptr(ret_addr)); + + const u64 mask = instance == AJM_INSTANCE_STATISTICS ? 0xc0018007ULL : 0x60000000e7ffULL; + flags.raw &= mask; + + const bool is_debug = ret_addr != nullptr; + batch_pos->opcode.instance = instance; + batch_pos->opcode.codec_flags = flags.codec; + batch_pos->opcode.command_flags = flags.command; + batch_pos->opcode.sideband_flags = AjmJobSidebandFlags(flags.sideband); + batch_pos->opcode.is_debug = is_debug; + batch_pos->opcode.is_statistic = instance == AJM_INSTANCE_STATISTICS; + batch_pos->opcode.is_control = true; + + AjmInOutJob* job = nullptr; + if (ret_addr == nullptr) { + batch_pos->job_size = sizeof(AjmInOutJob); + job = &batch_pos->job; + } else { + batch_pos->job_size = sizeof(AjmInOutJob) + 16; + batch_pos->ret.unk1 = batch_pos->ret.unk1 & 0xfffffff0 | 6; + batch_pos->ret.unk2 = 0; + batch_pos->ret.ret_addr = ret_addr; + job = &batch_pos->ret.job; + } + + job->input.props &= 0xffffffe0; + job->input.props |= 2; + job->input.buf_size = in_size; + job->input.buffer = in_buffer; + job->unk1 = (job->unk1 & 0xfc000030) + ((flags.raw >> 0x1a) & 0x180000) + 3; + job->flags = u32(flags.raw); + job->output.props &= 0xffffffe0; + job->output.props |= 0x12; + job->output.buf_size = out_size; + job->output.buffer = out_buffer; + return ++job; } int PS4_SYSV_ABI sceAjmBatchJobInlineBuffer() { @@ -35,13 +119,124 @@ int PS4_SYSV_ABI sceAjmBatchJobRunBufferRa() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa() { - LOG_ERROR(Lib_Ajm, "(STUBBED) called"); - return ORBIS_OK; +void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(AjmMultiJob* batch_pos, u32 instance, AjmFlags flags, + const AjmBuffer* in_buffers, u64 num_in_buffers, + const AjmBuffer* out_buffers, u64 num_out_buffers, + void* sideband_output, u64 sideband_output_size, + const void* ret_addr) { + LOG_INFO(Lib_Ajm, "called instance = {}, flags = {:#x}, cmd = {}, sideband_cmd = {} num_input_buffers = {}, num_output_buffers = {}, " + "ret_addr = {}", instance, flags.raw, magic_enum::enum_name(AjmJobRunFlags(flags.command)), + magic_enum::enum_name(AjmJobSidebandFlags(flags.sideband)), + num_in_buffers, num_out_buffers, fmt::ptr(ret_addr)); + const u32 job_size = (num_in_buffers * 2 + 1 + num_out_buffers * 2) * 8; + const bool is_debug = ret_addr != nullptr; + batch_pos->opcode.instance = instance; + batch_pos->opcode.codec_flags = flags.codec; + batch_pos->opcode.command_flags = flags.command; + batch_pos->opcode.sideband_flags = AjmJobSidebandFlags(flags.sideband); + batch_pos->opcode.is_debug = is_debug; + batch_pos->opcode.is_statistic = false; + batch_pos->opcode.is_control = false; + + u32* job = nullptr; + if (!is_debug) { + batch_pos->job_size = job_size + 16; + job = batch_pos->job; + } else { + batch_pos->job_size = job_size + 32; + batch_pos->ret.unk1 &= 0xfffffff0; + batch_pos->ret.unk1 |= 6; + batch_pos->ret.unk2 = 0; + batch_pos->ret.ret_addr = ret_addr; + job = batch_pos->ret.job; + } + + for (s32 i = 0; i < num_in_buffers; i++) { + AjmJobBuffer* in_buf = reinterpret_cast(job); + in_buf->props &= 0xffffffe0; + in_buf->props |= 1; + in_buf->buf_size = in_buffers[i].size; + in_buf->buffer = in_buffers[i].addr; + job += 4; + } + job[1] = u32(flags.raw & 0xe00000001fffULL); + job[0] &= 0xfc000030; + job[0] = s32((flags.raw & 0xe00000001fffULL) >> 0x1a) + 4; + job += 2; + + for (s32 i = 0; i < num_out_buffers; i++) { + AjmJobBuffer* out_buf = reinterpret_cast(job); + out_buf->props &= 0xffffffe0; + out_buf->props |= 0x11; + out_buf->buf_size = out_buffers[i].size; + out_buf->buffer = out_buffers[i].addr; + job += 4; + } + job[0] = job[0] & 0xffffffe0 | 0x12; + job[1] = sideband_output_size; + memcpy(&job[2], &sideband_output, sizeof(void*)); + return job + 4; } -int PS4_SYSV_ABI sceAjmBatchStartBuffer() { - LOG_ERROR(Lib_Ajm, "(STUBBED) called"); +int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context, const u8* batch, u32 batch_size, const int priority, + AjmBatchError* patch_error, u32* out_batch_id) { + LOG_DEBUG(Lib_Ajm, "called context = {}, batch_size = {:#x}, priority = {}", + context, batch_size, priority); + + if ((batch_size & 7) != 0) { + return ORBIS_AJM_ERROR_MALFORMED_BATCH; + } + + static constexpr u32 MaxBatches = 1000; + + struct BatchInfo { + u16 instance; + u16 offset_in_qwords; + }; + std::array batches{}; + u32 num_batches = 0; + + const u8* batch_ptr = batch; + const u8* batch_end = batch + batch_size; + while (batch_ptr < batch_end) { + if (num_batches >= MaxBatches) { + LOG_ERROR(Lib_Ajm, "Too many batches in job!"); + return ORBIS_AJM_ERROR_OUT_OF_MEMORY; + } + AjmJobHeader header; + std::memcpy(&header, batch_ptr, sizeof(u64)); + + const auto& opcode = header.opcode; + if (opcode.is_control) { + ASSERT_MSG(!opcode.is_statistic, "Statistic instance is not handled"); + const auto command = AjmJobControlFlags(opcode.command_flags); + switch (command) { + case AjmJobControlFlags::Reset: + LOG_INFO(Lib_Ajm, "Resetting instance {}", opcode.instance); + break; + case (AjmJobControlFlags::Initialize | AjmJobControlFlags::Reset): + LOG_INFO(Lib_Ajm, "Initializing instance {}", opcode.instance); + break; + case AjmJobControlFlags::Resample: + LOG_INFO(Lib_Ajm, "Set resample params of instance {}", opcode.instance); + break; + default: + break; + } + } else { + const auto command = AjmJobRunFlags(opcode.command_flags); + const auto sideband = AjmJobSidebandFlags(opcode.sideband_flags); + const u8* job_ptr = batch_ptr + sizeof(AjmJobHeader) + opcode.is_debug * 16; + const AjmJobBuffer* in_buffer = reinterpret_cast(job_ptr); + LOG_INFO(Lib_Ajm, "Decode job cmd = {}, sideband = {}, in_addr = {}, in_size = {}", + magic_enum::enum_name(command), magic_enum::enum_name(sideband), + fmt::ptr(in_buffer->buffer), in_buffer->buf_size); + dev->mp3dec.Decode(in_buffer->buffer, in_buffer->buf_size); + } + + batch_ptr += sizeof(AjmJobHeader) + header.job_size; + } + return ORBIS_OK; } @@ -55,9 +250,16 @@ int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAjmDecMp3ParseFrame() { - LOG_ERROR(Lib_Ajm, "(STUBBED) called"); - return ORBIS_OK; +int PS4_SYSV_ABI sceAjmDecMp3ParseFrame(const u8* buf, u32 stream_size, int parse_ofl, + AjmDecMp3ParseFrame* frame) { + LOG_INFO(Lib_Ajm, "called parse_ofl = {}", 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 ParseMp3Header(buf, stream_size, parse_ofl, frame); } int PS4_SYSV_ABI sceAjmFinalize() { @@ -65,8 +267,13 @@ int PS4_SYSV_ABI sceAjmFinalize() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAjmInitialize() { - LOG_ERROR(Lib_Ajm, "(STUBBED) called"); +int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* out_context) { + LOG_INFO(Lib_Ajm, "called reserved = {}", reserved); + if (out_context == nullptr || reserved != 0) { + return ORBIS_AJM_ERROR_INVALID_PARAMETER; + } + *out_context = 1; + dev = std::make_unique(); return ORBIS_OK; } @@ -75,13 +282,39 @@ int PS4_SYSV_ABI sceAjmInstanceCodecType() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAjmInstanceCreate() { - LOG_ERROR(Lib_Ajm, "(STUBBED) called"); +int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context, AjmCodecType codec_type, AjmInstanceFlags flags, u32* instance) { + if (codec_type >= AjmCodecType::Max) { + return ORBIS_AJM_ERROR_INVALID_PARAMETER; + } + if (flags.version == 0) { + return ORBIS_AJM_ERROR_WRONG_REVISION_FLAG; + } + if (!dev->IsRegistered(codec_type)) { + return ORBIS_AJM_ERROR_CODEC_NOT_REGISTERED; + } + if (dev->curr_cursor == dev->release_cursor) { + return ORBIS_AJM_ERROR_OUT_OF_RESOURCES; + } + const u32 index = dev->free_instances[dev->curr_cursor++]; + dev->curr_cursor %= MaxInstances; + dev->instances[index].codec_type = codec_type; + *instance = index; + LOG_INFO(Lib_Ajm, "called codec_type = {}, flags = {:#x}, instance = {}", + magic_enum::enum_name(codec_type), flags.raw, index); + return ORBIS_OK; } -int PS4_SYSV_ABI sceAjmInstanceDestroy() { - LOG_ERROR(Lib_Ajm, "(STUBBED) called"); +int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context, u32 instance) { + LOG_INFO(Lib_Ajm, "called context = {}, instance = {}", context, instance); + if ((instance & 0x3fff) > MaxInstances) { + return ORBIS_AJM_ERROR_INVALID_INSTANCE; + } + const u32 next_slot = (dev->release_cursor + 1) % MaxInstances; + if (next_slot != dev->curr_cursor) { + dev->free_instances[dev->release_cursor] = instance; + dev->release_cursor = next_slot; + } return ORBIS_OK; } @@ -105,8 +338,15 @@ int PS4_SYSV_ABI sceAjmMemoryUnregister() { return ORBIS_OK; } -int PS4_SYSV_ABI sceAjmModuleRegister() { - LOG_ERROR(Lib_Ajm, "(STUBBED) called"); +int PS4_SYSV_ABI sceAjmModuleRegister(u32 context, AjmCodecType codec_type, s64 reserved) { + LOG_INFO(Lib_Ajm, "called context = {}, codec_type = {}, reserved = {}", context, u32(codec_type), reserved); + if (codec_type >= AjmCodecType::Max || reserved != 0) { + return ORBIS_AJM_ERROR_INVALID_PARAMETER; + } + if (dev->IsRegistered(codec_type)) { + return ORBIS_AJM_ERROR_CODEC_ALREADY_REGISTERED; + } + dev->Register(codec_type); return ORBIS_OK; } @@ -145,4 +385,4 @@ void RegisterlibSceAjm(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("AxhcqVv5AYU", "libSceAjm", 1, "libSceAjm", 1, 1, sceAjmStrError); }; -} // namespace Libraries::Ajm \ No newline at end of file +} // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm.h b/src/core/libraries/ajm/ajm.h index 8491e519c..a78dc0956 100644 --- a/src/core/libraries/ajm/ajm.h +++ b/src/core/libraries/ajm/ajm.h @@ -4,6 +4,8 @@ #pragma once #include "common/types.h" +#include "common/enum.h" +#include "core/libraries/ajm/ajm_mp3.h" namespace Core::Loader { class SymbolsResolver; @@ -11,28 +13,162 @@ class SymbolsResolver; namespace Libraries::Ajm { +struct AjmBatchInfo { + void* pBuffer; + u64 offset; + u64 size; +}; + +struct AjmBatchError { + int error_code; + const void* job_addr; + u32 cmd_offset; + const void* job_ra; +}; + +struct AjmBuffer { + u8* addr; + u64 size; +}; + +struct AjmJobBuffer { + u32 props; + u32 buf_size; + const u8* buffer; +}; + +struct AjmInOutJob { + AjmJobBuffer input; + u32 unk1; + u32 flags; + AjmJobBuffer output; +}; + +enum class AjmJobControlFlags : u32 { + Reset = 1 << 2, + Initialize = 1 << 3, + Resample = 1 << 4, +}; +DECLARE_ENUM_FLAG_OPERATORS(AjmJobControlFlags) + +enum class AjmJobRunFlags : u32 { + GetCodecInfo = 1 << 0, + MultipleFrames = 1 << 1, +}; + +enum class AjmJobSidebandFlags : u32 { + GaplessDecode = 1 << 0, + GetInfo = 1 << 1, + Stream = 1 << 2, +}; +DECLARE_ENUM_FLAG_OPERATORS(AjmJobSidebandFlags) + +struct AjmHLEOpcode { + u32 instance : 14; + u32 codec_flags : 8; + u32 command_flags : 4; + AjmJobSidebandFlags sideband_flags : 3; + u32 is_statistic : 1; + u32 is_debug : 1; + u32 is_control : 1; +}; + +struct AjmJobHeader { + AjmHLEOpcode opcode; + u32 job_size; +}; + +struct AjmSingleJob { + AjmHLEOpcode opcode; + u32 job_size; + union { + AjmInOutJob job; + struct { + u32 unk1; + u32 unk2; + const void* ret_addr; + AjmInOutJob job; + } ret; + }; +}; + +struct AjmMultiJob { + AjmHLEOpcode opcode; + u32 job_size; + union { + u32 job[]; + struct { + u32 unk1; + u32 unk2; + const void* ret_addr; + u32 job[]; + } ret; + }; +}; + +enum class AjmCodecType : u32 { + Mp3Dec = 0, + At9Dec = 1, + M4aacDec = 2, + Max = 23, +}; +static constexpr u32 NumAjmCodecs = u32(AjmCodecType::Max); + +union AjmFlags { + u64 raw; + struct { + u64 version : 3; + u64 codec : 8; + u64 command : 4; + u64 reserved : 30; + u64 sideband : 3; + }; +}; + +union AjmInstanceFlags { + u64 raw; + struct { + u64 version : 3; + u64 channels : 4; + u64 format : 3; + u64 pad : 22; + u64 codec : 28; + }; +}; + int PS4_SYSV_ABI sceAjmBatchCancel(); int PS4_SYSV_ABI sceAjmBatchErrorDump(); -int PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(); +void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(AjmSingleJob* batch_pos, u32 instance, AjmFlags flags, + const u8* in_buffer, u32 in_size, u8* out_buffer, + u32 out_size, const void* ret_addr); int PS4_SYSV_ABI sceAjmBatchJobInlineBuffer(); int PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(); -int PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(); -int PS4_SYSV_ABI sceAjmBatchStartBuffer(); +void* PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa(AjmMultiJob* batch_pos, u32 instance, AjmFlags flags, + const AjmBuffer* input_buffers, + u64 num_input_buffers, + const AjmBuffer* output_buffers, + u64 num_output_buffers, + void* sideband_output, + u64 sideband_output_size, + const void* ret_addr); +int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context, const u8* batch, u32 batch_size, const int priority, + AjmBatchError* patch_error, u32* out_batch_id); int PS4_SYSV_ABI sceAjmBatchWait(); int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData(); -int PS4_SYSV_ABI sceAjmDecMp3ParseFrame(); +int PS4_SYSV_ABI sceAjmDecMp3ParseFrame(const u8* stream, u32 stream_size, int parse_ofl, + AjmDecMp3ParseFrame* frame); int PS4_SYSV_ABI sceAjmFinalize(); -int PS4_SYSV_ABI sceAjmInitialize(); +int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* out_context); int PS4_SYSV_ABI sceAjmInstanceCodecType(); -int PS4_SYSV_ABI sceAjmInstanceCreate(); -int PS4_SYSV_ABI sceAjmInstanceDestroy(); +int PS4_SYSV_ABI sceAjmInstanceCreate(u32 context, AjmCodecType codec_type, AjmInstanceFlags flags, u32* instance); +int PS4_SYSV_ABI sceAjmInstanceDestroy(u32 context, u32 instance); int PS4_SYSV_ABI sceAjmInstanceExtend(); int PS4_SYSV_ABI sceAjmInstanceSwitch(); int PS4_SYSV_ABI sceAjmMemoryRegister(); int PS4_SYSV_ABI sceAjmMemoryUnregister(); -int PS4_SYSV_ABI sceAjmModuleRegister(); +int PS4_SYSV_ABI sceAjmModuleRegister(u32 context, AjmCodecType codec_type, s64 reserved); int PS4_SYSV_ABI sceAjmModuleUnregister(); int PS4_SYSV_ABI sceAjmStrError(); void RegisterlibSceAjm(Core::Loader::SymbolsResolver* sym); -} // namespace Libraries::Ajm \ No newline at end of file +} // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_mp3.cpp b/src/core/libraries/ajm/ajm_mp3.cpp new file mode 100644 index 000000000..06305ee84 --- /dev/null +++ b/src/core/libraries/ajm/ajm_mp3.cpp @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma clang optimize off +#include "common/assert.h" +#include "core/libraries/ajm/ajm_error.h" +#include "core/libraries/ajm/ajm_mp3.h" + +extern "C" { +#include +#include +#include +} + +namespace Libraries::Ajm { + +// Following tables have been reversed from AJM library +static constexpr std::array, 3> SamplerateTable = {{ + {0x5622, 0x5DC0, 0x3E80}, {0xAC44, 0xBB80, 0x7D00}, {0x2B11, 0x2EE0, 0x1F40}, +}}; + +static constexpr std::array, 2> BitrateTable = {{ + {0, 0x20, 0x28, 0x30, 0x38, 0x40, 0x50, 0x60, 0x70, 0x80, 0xA0, 0xC0, 0xE0, 0x100, 0x140}, + {0, 0x8, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0}, +}}; + +static constexpr std::array UnkTable = {0x48, 0x90}; + +int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl, + AjmDecMp3ParseFrame* frame) { + 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; + const s32 br_idx = (buf[2] >> 4) & 0xf; + const s32 padding_bit = (buf[2] >> 1) & 0x1; + + frame->sample_rate = SamplerateTable[version_idx][sr_idx]; + frame->bitrate = BitrateTable[version_idx != 1][br_idx] * 1000; + frame->num_channels = (buf[3] < 0xc0) + 1; + 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; +} + +MP3Decoder::MP3Decoder() { + codec = avcodec_find_decoder(AV_CODEC_ID_MP3); + ASSERT_MSG(codec, "MP3 codec not found"); + parser = av_parser_init(codec->id); + ASSERT_MSG(parser, "Parser not found"); + c = avcodec_alloc_context3(codec); + ASSERT_MSG(c, "Could not allocate audio codec context"); + int ret = avcodec_open2(c, codec, nullptr); + ASSERT_MSG(ret >= 0, "Could not open codec"); +} + +MP3Decoder::~MP3Decoder() { + avcodec_free_context(&c); + av_free(c); +} + +void MP3Decoder::Decode(const u8* buf, u32 buf_size) { + AVPacket* pkt = av_packet_alloc(); + AVFrame* frame = av_frame_alloc(); + while (buf_size > 0) { + int ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size, + buf, buf_size, + AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); + ASSERT_MSG(ret >= 0, "Error while parsing {}", ret); + buf += ret; + buf_size -= 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); + + // Read all the output frames (in general there may be any number of them + while (ret >= 0) { + LOG_INFO(Lib_Ajm, "Receive MP3 frame"); + ret = avcodec_receive_frame(c, frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } else if (ret < 0) { + UNREACHABLE_MSG("Error during decoding"); + } + const s32 bps = av_get_bytes_per_sample(c->sample_fmt); + } + } + } + av_frame_free(&frame); + av_packet_free(&pkt); +} + +} // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_mp3.h b/src/core/libraries/ajm/ajm_mp3.h new file mode 100644 index 000000000..abd78f56d --- /dev/null +++ b/src/core/libraries/ajm/ajm_mp3.h @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +extern "C" { +struct AVCodec; +struct AVCodecContext; +struct AVCodecParserContext; +} + +namespace Libraries::Ajm { + +enum class AjmDecMp3OflType : u32 { + None = 0, + Lame = 1, + Vbri = 2, + Fgh = 3, + VbriAndFgh = 4 +}; + +// 11-bit syncword if MPEG 2.5 extensions are enabled +static constexpr u8 SYNCWORDH = 0xff; +static constexpr u8 SYNCWORDL = 0xe0; + +struct AjmDecMp3ParseFrame { + u64 frame_size; + u32 num_channels; + u32 samples_per_channel; + u32 bitrate; + u32 sample_rate; + u32 encoder_delay; + u32 num_frames; + u32 total_samples; + AjmDecMp3OflType ofl_type; +}; + +enum class ChannelMode : u8 { + Stereo = 0, + JointStero = 1, + Dual = 2, + Mono = 3, +}; + +struct AjmSidebandDecMp3CodecInfo { + u32 header; + bool has_crc; + ChannelMode channel_mode; + u8 mode_extension; + u8 copyright; + u8 original; + u8 emphasis; + u16 reserved[3]; +}; + +struct AjmSidebandResult { + s32 result; + s32 internal_result; +}; + +struct AjmDecMp3GetCodecInfoResult { + AjmSidebandResult result; + AjmSidebandDecMp3CodecInfo codec_info; +}; + +struct MP3Decoder { + const AVCodec* codec = nullptr; + AVCodecContext* c = nullptr; + AVCodecParserContext* parser = nullptr; + + explicit MP3Decoder(); + ~MP3Decoder(); + + void Decode(const u8* in_buf, u32 frame_size); +}; + +int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl, + AjmDecMp3ParseFrame* frame); + +} // namespace Libraries::Ajm