ajm: Some work

This commit is contained in:
IndecisiveTurtle 2024-09-13 17:22:51 +03:00
parent b0bbb16aae
commit e19e471158
6 changed files with 595 additions and 32 deletions

3
.gitmodules vendored
View file

@ -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

1
externals/LibAtrac9 vendored Submodule

@ -0,0 +1 @@
Subproject commit 82767fe38823c32536726ea798f392b0b49e66b9

View file

@ -1,15 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma clang optimize off
#include <numeric>
#include <magic_enum.hpp>
#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 <libatrac9.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libswresample/swresample.h>
#include <structures.h>
#include <error_codes.h>
}
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<bool, NumAjmCodecs> is_registered{};
std::array<u32, MaxInstances> free_instances{};
std::array<AjmInstance, MaxInstances> instances{};
MP3Decoder mp3dec;
bool IsRegistered(AjmCodecType type) const {
return is_registered[static_cast<u32>(type)];
}
void Register(AjmCodecType type) {
is_registered[static_cast<u32>(type)] = true;
}
AjmDevice() {
std::iota(free_instances.begin(), free_instances.end(), 1);
}
};
static std::unique_ptr<AjmDevice> 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<AjmJobBuffer*>(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<AjmJobBuffer*>(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<BatchInfo, MaxBatches> 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<const AjmJobBuffer*>(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<AjmDevice>();
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
} // namespace Libraries::Ajm

View file

@ -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
} // namespace Libraries::Ajm

View file

@ -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 <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libswresample/swresample.h>
}
namespace Libraries::Ajm {
// Following tables have been reversed from AJM library
static constexpr std::array<std::array<s32, 3>, 3> SamplerateTable = {{
{0x5622, 0x5DC0, 0x3E80}, {0xAC44, 0xBB80, 0x7D00}, {0x2B11, 0x2EE0, 0x1F40},
}};
static constexpr std::array<std::array<s32, 15>, 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<s32, 2> 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

View file

@ -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