mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2024-11-23 03:09:55 +00:00
ajm: Initial ajm implementation
This commit is contained in:
parent
8b2c151f1f
commit
b16b1b9f70
5
.gitmodules
vendored
5
.gitmodules
vendored
@ -98,4 +98,7 @@
|
||||
[submodule "externals/discord-rpc"]
|
||||
path = externals/discord-rpc
|
||||
url = https://github.com/shadps4-emu/ext-discord-rpc.git
|
||||
shallow = true
|
||||
shallow = true
|
||||
[submodule "externals/LibAtrac9"]
|
||||
path = externals/LibAtrac9
|
||||
url = https://github.com/Vita3K/LibAtrac9
|
||||
|
@ -185,6 +185,12 @@ set(AUDIO_LIB src/core/libraries/audio/audioin.cpp
|
||||
src/core/libraries/audio/audioout.h
|
||||
src/core/libraries/ajm/ajm.cpp
|
||||
src/core/libraries/ajm/ajm.h
|
||||
src/core/libraries/ajm/ajm_instance.h
|
||||
src/core/libraries/ajm/ajm_error.h
|
||||
src/core/libraries/ajm/ajm_mp3.cpp
|
||||
src/core/libraries/ajm/ajm_mp3.h
|
||||
src/core/libraries/ajm/ajm_at9.cpp
|
||||
src/core/libraries/ajm/ajm_at9.h
|
||||
src/core/libraries/ngs2/ngs2.cpp
|
||||
src/core/libraries/ngs2/ngs2.h
|
||||
)
|
||||
@ -194,8 +200,7 @@ set(GNM_LIB src/core/libraries/gnmdriver/gnmdriver.cpp
|
||||
src/core/libraries/gnmdriver/gnm_error.h
|
||||
)
|
||||
|
||||
set(KERNEL_LIB
|
||||
src/core/libraries/kernel/event_flag/event_flag.cpp
|
||||
set(KERNEL_LIB src/core/libraries/kernel/event_flag/event_flag.cpp
|
||||
src/core/libraries/kernel/event_flag/event_flag.h
|
||||
src/core/libraries/kernel/event_flag/event_flag_obj.cpp
|
||||
src/core/libraries/kernel/event_flag/event_flag_obj.h
|
||||
@ -794,7 +799,7 @@ endif()
|
||||
create_target_directory_groups(shadps4)
|
||||
|
||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half)
|
||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3 pugixml::pugixml)
|
||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::SPIRV glslang::glslang SDL3::SDL3 pugixml::pugixml)
|
||||
|
||||
target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")
|
||||
target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h")
|
||||
|
5
externals/CMakeLists.txt
vendored
5
externals/CMakeLists.txt
vendored
@ -47,6 +47,11 @@ if (NOT TARGET FFmpeg::ffmpeg)
|
||||
add_library(FFmpeg::ffmpeg ALIAS ffmpeg)
|
||||
endif()
|
||||
|
||||
# LibAtrac9
|
||||
file(GLOB LIBATRAC9_SOURCES LibAtrac9/C/src/*.c)
|
||||
add_library(LibAtrac9 STATIC ${LIBATRAC9_SOURCES})
|
||||
target_include_directories(LibAtrac9 INTERFACE LibAtrac9/C/src)
|
||||
|
||||
# Zlib-Ng
|
||||
if (NOT TARGET zlib-ng::zlib)
|
||||
set(ZLIB_ENABLE_TESTS OFF)
|
||||
|
1
externals/LibAtrac9
vendored
Submodule
1
externals/LibAtrac9
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 82767fe38823c32536726ea798f392b0b49e66b9
|
@ -1,15 +1,76 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "ajm.h"
|
||||
#include "ajm_error.h"
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#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_error.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
#include "core/libraries/ajm/ajm_mp3.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>
|
||||
}
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
static constexpr u32 AJM_INSTANCE_STATISTICS = 0x80000;
|
||||
|
||||
static constexpr u32 SCE_AJM_WAIT_INFINITE = -1;
|
||||
|
||||
static constexpr u32 MaxInstances = 0x2fff;
|
||||
|
||||
static constexpr u32 MaxBatches = 1000;
|
||||
|
||||
struct BatchInfo {
|
||||
u16 instance{};
|
||||
u16 offset_in_qwords{}; // Needed for AjmBatchError?
|
||||
bool waiting{};
|
||||
bool finished{};
|
||||
std::mutex mtx;
|
||||
std::condition_variable cv;
|
||||
int result{};
|
||||
};
|
||||
|
||||
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<std::unique_ptr<AjmInstance>, MaxInstances> instances;
|
||||
std::vector<std::shared_ptr<BatchInfo>> batches{};
|
||||
std::mutex batches_mutex;
|
||||
|
||||
[[nodiscard]] 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,34 +81,322 @@ int PS4_SYSV_ABI sceAjmBatchErrorDump() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAjmBatchJobControlBufferRa() {
|
||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
||||
void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(AjmControlJob* batch_pos, u32 instance,
|
||||
AjmFlags flags, u8* in_buffer, u32 in_size,
|
||||
u8* out_buffer, u32 out_size, 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(flags.control_flags), in_size, out_size,
|
||||
fmt::ptr(ret_addr));
|
||||
|
||||
const u64 mask = instance == AJM_INSTANCE_STATISTICS ? 0xc0018007ULL : 0x60000000e7ffULL;
|
||||
flags.raw &= mask;
|
||||
|
||||
batch_pos->header.instance = instance;
|
||||
|
||||
AjmControlJobInner* job;
|
||||
if (ret_addr == nullptr) {
|
||||
batch_pos->header.job_size = sizeof(AjmControlJobInner);
|
||||
job = &batch_pos->job;
|
||||
} else {
|
||||
batch_pos->header.job_size = sizeof(AjmControlJobInner) + sizeof(AjmJobBuffer);
|
||||
batch_pos->ret.ret_buf.ident = Identifier::ReturnAddrBuf;
|
||||
batch_pos->ret.ret_buf.buf_size = 0;
|
||||
batch_pos->ret.ret_buf.buffer = (u8*)ret_addr;
|
||||
job = &batch_pos->ret.job;
|
||||
}
|
||||
|
||||
job->input.ident = Identifier::InputControlBuf;
|
||||
job->input.buf_size = in_size;
|
||||
job->input.buffer = in_buffer;
|
||||
job->flags.raw1 = (job->flags.raw1 & 0xfc000000) + ((flags.raw >> 0x1a) & 0x180000) + 3;
|
||||
job->flags.raw2 = u32(flags.raw);
|
||||
job->output.ident = Identifier::OutputRunControlBuf;
|
||||
job->output.buf_size = out_size;
|
||||
job->output.buffer = out_buffer;
|
||||
return ++job;
|
||||
}
|
||||
|
||||
void* PS4_SYSV_ABI sceAjmBatchJobInlineBuffer(u8* batch_pos, const void* in_buffer, size_t in_size,
|
||||
const void** batch_address) {
|
||||
// TODO
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(AjmRunJob* batch_pos, u32 instance, AjmFlags flags,
|
||||
u8* in_buffer, u32 in_size, u8* out_buffer,
|
||||
const u32 out_size, u8* sideband_output,
|
||||
const u32 sideband_output_size, 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(flags.run_flags), in_size, out_size,
|
||||
fmt::ptr(ret_addr));
|
||||
|
||||
const u64 mask = 0xE00000001FFFLL;
|
||||
flags.raw &= mask;
|
||||
|
||||
batch_pos->header.instance = instance;
|
||||
|
||||
AjmRunJobInner* job;
|
||||
if (ret_addr == nullptr) {
|
||||
batch_pos->header.job_size = sizeof(AjmRunJobInner);
|
||||
job = &batch_pos->job;
|
||||
} else {
|
||||
batch_pos->header.job_size = sizeof(AjmRunJobInner) + sizeof(AjmJobBuffer);
|
||||
batch_pos->ret.ret_buf.ident = Identifier::ReturnAddrBuf;
|
||||
batch_pos->ret.ret_buf.buf_size = 0;
|
||||
batch_pos->ret.ret_buf.buffer = (u8*)ret_addr;
|
||||
job = &batch_pos->ret.job;
|
||||
}
|
||||
|
||||
job->input.ident = Identifier::InputRunBuf;
|
||||
job->input.buf_size = in_size;
|
||||
job->input.buffer = in_buffer;
|
||||
job->flags.raw1 = (job->flags.raw1 & 0xfc000000) + (flags.raw >> 0x1a) + 4;
|
||||
job->flags.raw2 = u32(flags.raw);
|
||||
job->output.ident = Identifier::OutputRunControlBuf;
|
||||
job->output.buf_size = out_size;
|
||||
job->output.buffer = out_buffer;
|
||||
job->sideband.ident = Identifier::OutputRunControlBuf;
|
||||
job->sideband.buf_size = sideband_output_size;
|
||||
job->sideband.buffer = sideband_output;
|
||||
return ++job;
|
||||
}
|
||||
|
||||
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, 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(flags.run_flags),
|
||||
magic_enum::enum_name(flags.sideband_flags), num_in_buffers, num_out_buffers,
|
||||
fmt::ptr(ret_addr));
|
||||
|
||||
const u32 job_size = (num_in_buffers * 2 + 1 + num_out_buffers * 2) * 8;
|
||||
batch_pos->header.instance = instance;
|
||||
|
||||
u32* job;
|
||||
if (ret_addr == nullptr) {
|
||||
batch_pos->header.job_size = job_size + 16;
|
||||
job = batch_pos->job;
|
||||
} else {
|
||||
batch_pos->header.job_size = job_size + 32;
|
||||
batch_pos->ret.ret_buf.ident = Identifier::ReturnAddrBuf;
|
||||
batch_pos->ret.ret_buf.buf_size = 0;
|
||||
batch_pos->ret.ret_buf.buffer = (u8*)ret_addr;
|
||||
job = batch_pos->ret.job;
|
||||
}
|
||||
|
||||
for (s32 i = 0; i < num_in_buffers; i++) {
|
||||
auto* in_buf = reinterpret_cast<AjmJobBuffer*>(job);
|
||||
in_buf->ident = Identifier::InputRunBuf;
|
||||
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++) {
|
||||
auto* out_buf = reinterpret_cast<AjmJobBuffer*>(job);
|
||||
out_buf->ident = Identifier::OutputMultijobBuf;
|
||||
out_buf->buf_size = out_buffers[i].size;
|
||||
out_buf->buffer = out_buffers[i].addr;
|
||||
job += 4;
|
||||
}
|
||||
job[0] = job[0] & 0xffffffe0 | 0x12; // output.ident
|
||||
job[1] = sideband_output_size; // output.buf_size
|
||||
memcpy(&job[2], &sideband_output, sizeof(void*)); // output.buffer
|
||||
return job + 4;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context, const u8* batch, u32 batch_size,
|
||||
const int priority, AjmBatchError* batch_error,
|
||||
u32* out_batch_id) {
|
||||
LOG_INFO(Lib_Ajm, "called context = {}, batch_size = {:#x}, priority = {}", context, batch_size,
|
||||
priority);
|
||||
|
||||
if ((batch_size & 7) != 0) {
|
||||
return ORBIS_AJM_ERROR_MALFORMED_BATCH;
|
||||
}
|
||||
|
||||
const auto batch_info = std::make_shared<BatchInfo>();
|
||||
if (dev->batches.size() >= MaxBatches) {
|
||||
LOG_ERROR(Lib_Ajm, "Too many batches in job!");
|
||||
return ORBIS_AJM_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
*out_batch_id = static_cast<u32>(dev->batches.size());
|
||||
dev->batches.push_back(batch_info);
|
||||
|
||||
const u8* batch_ptr = batch;
|
||||
const u8* batch_end = batch + batch_size;
|
||||
AjmJobHeader header{};
|
||||
|
||||
while (batch_ptr < batch_end) {
|
||||
std::memcpy(&header, batch_ptr, sizeof(u64));
|
||||
const u32 instance = header.instance;
|
||||
|
||||
const u8* curr_ptr = batch_ptr + sizeof(AjmJobHeader);
|
||||
const u8* job_end = curr_ptr + header.job_size;
|
||||
|
||||
boost::container::small_vector<AjmJobBuffer, 4> input_buffers;
|
||||
while (true) {
|
||||
Identifier ident{};
|
||||
std::memcpy(&ident, curr_ptr, sizeof(u8));
|
||||
|
||||
// Ignore return address buffers.
|
||||
if (ident == Identifier::ReturnAddrBuf) {
|
||||
curr_ptr += sizeof(AjmJobBuffer);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add input buffer to the list of inputs.
|
||||
if (ident == Identifier::InputRunBuf || ident == Identifier::InputControlBuf) {
|
||||
auto& buffer = input_buffers.emplace_back();
|
||||
std::memcpy(&buffer, curr_ptr, sizeof(buffer));
|
||||
curr_ptr += sizeof(AjmJobBuffer);
|
||||
continue;
|
||||
}
|
||||
|
||||
// A control or run flags identifier stops input buffer collection
|
||||
// and provides necessary flags for the operation requested.
|
||||
if (ident == Identifier::ControlFlags) {
|
||||
AjmFlagsIdentifier flags;
|
||||
std::memcpy(&flags, curr_ptr, sizeof(flags));
|
||||
|
||||
ASSERT_MSG(input_buffers.size() == 1,
|
||||
"Only 1 input buffer is allowed for control commands");
|
||||
const auto& in_buffer = input_buffers.back();
|
||||
|
||||
const auto command = AjmJobControlFlags(flags.control_flags.Value());
|
||||
if (True(command & AjmJobControlFlags::Reset)) {
|
||||
LOG_INFO(Lib_Ajm, "Resetting instance {}", instance);
|
||||
dev->instances[instance]->Reset();
|
||||
}
|
||||
if (True(command & AjmJobControlFlags::Initialize)) {
|
||||
LOG_INFO(Lib_Ajm, "Initializing instance {}", instance);
|
||||
dev->instances[instance]->Initialize(in_buffer.buffer, in_buffer.buf_size);
|
||||
}
|
||||
if (True(command & AjmJobControlFlags::Resample)) {
|
||||
LOG_WARNING(Lib_Ajm, "Set resample params of instance {}", instance);
|
||||
}
|
||||
|
||||
curr_ptr += sizeof(flags);
|
||||
AjmJobBuffer out_buffer;
|
||||
std::memcpy(&out_buffer, curr_ptr, sizeof(out_buffer));
|
||||
|
||||
// Write sideband structures.
|
||||
auto* result = reinterpret_cast<AjmSidebandResult*>(out_buffer.buffer);
|
||||
result->result = 0;
|
||||
result->internal_result = 0;
|
||||
break;
|
||||
}
|
||||
if (ident == Identifier::RunFlags) {
|
||||
AjmFlagsIdentifier flags;
|
||||
std::memcpy(&flags, curr_ptr, sizeof(flags));
|
||||
|
||||
const auto command = AjmJobRunFlags(flags.run_flags.Value());
|
||||
const auto sideband = AjmJobSidebandFlags(flags.sideband_flags.Value());
|
||||
curr_ptr += sizeof(flags);
|
||||
|
||||
// Collect output buffers.
|
||||
boost::container::small_vector<AjmJobBuffer, 4> output_buffers;
|
||||
while (curr_ptr < job_end) {
|
||||
auto& buffer = output_buffers.emplace_back();
|
||||
std::memcpy(&buffer, curr_ptr, sizeof(buffer));
|
||||
curr_ptr += sizeof(buffer);
|
||||
}
|
||||
|
||||
ASSERT_MSG(input_buffers.size() == 1 && output_buffers.size() == 2,
|
||||
"Run operation with multiple buffers untested in = {}, out = {}",
|
||||
input_buffers.size(), output_buffers.size());
|
||||
AjmJobBuffer in_buffer = input_buffers.back();
|
||||
AjmJobBuffer out_buffer = output_buffers.front();
|
||||
AjmJobBuffer sideband_buffer = output_buffers.back();
|
||||
|
||||
// Write sideband structures.
|
||||
auto* sideband_ptr = sideband_buffer.buffer;
|
||||
auto* result = reinterpret_cast<AjmSidebandResult*>(sideband_ptr);
|
||||
result->result = 0;
|
||||
result->internal_result = 0;
|
||||
sideband_ptr += sizeof(AjmSidebandResult);
|
||||
|
||||
// Perform operation requested by run flags.
|
||||
AjmInstance* decoder_instance = dev->instances[instance].get();
|
||||
if (True(command & AjmJobRunFlags::GetCodecInfo)) {
|
||||
decoder_instance->GetCodecInfo(sideband_ptr);
|
||||
} else {
|
||||
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);
|
||||
|
||||
// Decode as much of the input bitstream as possible.
|
||||
const auto [in_remain, out_remain, num_frames] =
|
||||
decoder_instance->Decode(in_buffer.buffer, in_buffer.buf_size,
|
||||
out_buffer.buffer, out_buffer.buf_size);
|
||||
|
||||
// Check sideband flags for decoding
|
||||
if (True(sideband & AjmJobSidebandFlags::Stream)) {
|
||||
auto* stream = reinterpret_cast<AjmSidebandStream*>(sideband_ptr);
|
||||
stream->input_consumed = in_buffer.buf_size - in_remain;
|
||||
stream->output_written = out_buffer.buf_size - out_remain;
|
||||
stream->total_decoded_samples = decoder_instance->decoded_samples;
|
||||
sideband_ptr += sizeof(AjmSidebandStream);
|
||||
}
|
||||
if (True(command & AjmJobRunFlags::MultipleFrames)) {
|
||||
auto* mframe = reinterpret_cast<AjmSidebandMFrame*>(sideband_ptr);
|
||||
mframe->num_frames = num_frames;
|
||||
sideband_ptr += sizeof(AjmSidebandMFrame);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
UNREACHABLE_MSG("Unknown ident = {}", u32(ident));
|
||||
}
|
||||
batch_ptr += sizeof(AjmJobHeader) + header.job_size;
|
||||
}
|
||||
|
||||
batch_info->finished = true;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAjmBatchJobInlineBuffer() {
|
||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
int PS4_SYSV_ABI sceAjmBatchWait(const u32 context, const u32 batch_id, const u32 timeout,
|
||||
AjmBatchError* const batch_error) {
|
||||
LOG_INFO(Lib_Ajm, "called context = {}, batch_id = {}, timeout = {}", context, batch_id,
|
||||
timeout);
|
||||
|
||||
int PS4_SYSV_ABI sceAjmBatchJobRunBufferRa() {
|
||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
if (batch_id > 0xFF || batch_id >= dev->batches.size()) {
|
||||
return ORBIS_AJM_ERROR_INVALID_BATCH;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa() {
|
||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
const auto& batch = dev->batches[batch_id];
|
||||
|
||||
int PS4_SYSV_ABI sceAjmBatchStartBuffer() {
|
||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
if (batch->waiting) {
|
||||
return ORBIS_AJM_ERROR_BUSY;
|
||||
}
|
||||
batch->waiting = true;
|
||||
|
||||
int PS4_SYSV_ABI sceAjmBatchWait() {
|
||||
LOG_ERROR(Lib_Ajm, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
{
|
||||
std::unique_lock lk{batch->mtx};
|
||||
if (!batch->cv.wait_for(lk, std::chrono::milliseconds(timeout),
|
||||
[&] { return batch->finished; })) {
|
||||
return ORBIS_AJM_ERROR_IN_PROGRESS;
|
||||
}
|
||||
}
|
||||
|
||||
dev->batches.erase(dev->batches.begin() + batch_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAjmDecAt9ParseConfigData() {
|
||||
@ -55,9 +404,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 AjmMp3Decoder::ParseMp3Header(buf, stream_size, parse_ofl, frame);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceAjmFinalize() {
|
||||
@ -65,8 +421,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 +436,56 @@ 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* out_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;
|
||||
}
|
||||
ASSERT_MSG(flags.format == 0, "Only signed 16-bit PCM output is supported currently!");
|
||||
const u32 index = dev->free_instances[dev->curr_cursor++];
|
||||
dev->curr_cursor %= MaxInstances;
|
||||
std::unique_ptr<AjmInstance> instance;
|
||||
switch (codec_type) {
|
||||
case AjmCodecType::Mp3Dec:
|
||||
instance = std::make_unique<AjmMp3Decoder>();
|
||||
break;
|
||||
case AjmCodecType::At9Dec:
|
||||
instance = std::make_unique<AjmAt9Decoder>();
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Codec #{} not implemented", u32(codec_type));
|
||||
}
|
||||
instance->index = index;
|
||||
instance->codec_type = codec_type;
|
||||
instance->num_channels = flags.channels;
|
||||
dev->instances[index] = std::move(instance);
|
||||
*out_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;
|
||||
}
|
||||
dev->instances[instance].reset();
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -105,8 +509,16 @@ 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 +557,4 @@ void RegisterlibSceAjm(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("AxhcqVv5AYU", "libSceAjm", 1, "libSceAjm", 1, 1, sceAjmStrError);
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ajm
|
||||
} // namespace Libraries::Ajm
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/enum.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
@ -11,28 +13,193 @@ 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;
|
||||
};
|
||||
|
||||
enum class Identifier : u32 {
|
||||
InputRunBuf = 1,
|
||||
InputControlBuf = 2,
|
||||
ControlFlags = 3,
|
||||
RunFlags = 4,
|
||||
ReturnAddrBuf = 6,
|
||||
OutputMultijobBuf = 17,
|
||||
OutputRunControlBuf = 18,
|
||||
IdentMask = 0xff,
|
||||
};
|
||||
|
||||
struct AjmJobBuffer {
|
||||
Identifier ident;
|
||||
u32 buf_size;
|
||||
u8* buffer;
|
||||
};
|
||||
|
||||
enum class AjmJobControlFlags : u64 {
|
||||
Reset = 1 << 0,
|
||||
Initialize = 1 << 1,
|
||||
Resample = 1 << 2,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(AjmJobControlFlags)
|
||||
|
||||
enum class AjmJobRunFlags : u64 {
|
||||
GetCodecInfo = 1 << 0,
|
||||
MultipleFrames = 1 << 1,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(AjmJobRunFlags)
|
||||
|
||||
enum class AjmJobSidebandFlags : u64 {
|
||||
GaplessDecode = 1 << 0,
|
||||
GetInfo = 1 << 1,
|
||||
Stream = 1 << 2,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(AjmJobSidebandFlags)
|
||||
|
||||
union AjmFlags {
|
||||
u64 raw;
|
||||
struct {
|
||||
u64 version : 3;
|
||||
u64 codec : 8;
|
||||
AjmJobRunFlags run_flags : 2;
|
||||
AjmJobControlFlags control_flags : 3;
|
||||
u64 reserved : 29;
|
||||
AjmJobSidebandFlags sideband_flags : 3;
|
||||
};
|
||||
};
|
||||
|
||||
struct AjmFlagsIdentifier {
|
||||
union {
|
||||
u32 raw1;
|
||||
BitField<0, 4, Identifier> identifier;
|
||||
BitField<19, 3, u32> sideband_flags;
|
||||
};
|
||||
union {
|
||||
u32 raw2;
|
||||
BitField<0, 3, u32> version;
|
||||
BitField<3, 8, u32> codec;
|
||||
BitField<11, 2, u32> run_flags;
|
||||
BitField<13, 3, u32> control_flags;
|
||||
};
|
||||
};
|
||||
|
||||
struct AjmControlJobInner {
|
||||
AjmJobBuffer input;
|
||||
AjmFlagsIdentifier flags;
|
||||
AjmJobBuffer output;
|
||||
};
|
||||
|
||||
struct AjmRunJobInner : public AjmControlJobInner {
|
||||
AjmJobBuffer sideband;
|
||||
};
|
||||
|
||||
struct AjmJobHeader {
|
||||
struct {
|
||||
u32 : 6;
|
||||
u32 instance : 16;
|
||||
};
|
||||
u32 job_size;
|
||||
};
|
||||
|
||||
struct AjmControlJob {
|
||||
AjmJobHeader header;
|
||||
union {
|
||||
AjmControlJobInner job;
|
||||
struct {
|
||||
AjmJobBuffer ret_buf;
|
||||
AjmControlJobInner job;
|
||||
} ret;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(AjmControlJob) == 64);
|
||||
|
||||
struct AjmRunJob {
|
||||
AjmJobHeader header;
|
||||
union {
|
||||
AjmRunJobInner job;
|
||||
struct {
|
||||
AjmJobBuffer ret_buf;
|
||||
AjmRunJobInner job;
|
||||
} ret;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(AjmRunJob) == 80);
|
||||
|
||||
struct AjmMultiJob {
|
||||
AjmJobHeader header;
|
||||
union {
|
||||
u32 job[];
|
||||
struct {
|
||||
AjmJobBuffer ret_buf;
|
||||
u32 job[];
|
||||
} ret;
|
||||
};
|
||||
};
|
||||
|
||||
union AjmInstanceFlags {
|
||||
u64 raw;
|
||||
struct {
|
||||
u64 version : 3;
|
||||
u64 channels : 4;
|
||||
u64 format : 3;
|
||||
u64 pad : 22;
|
||||
u64 codec : 28;
|
||||
};
|
||||
};
|
||||
|
||||
struct AjmDecMp3ParseFrame;
|
||||
enum class AjmCodecType : u32;
|
||||
|
||||
int PS4_SYSV_ABI sceAjmBatchCancel();
|
||||
int PS4_SYSV_ABI sceAjmBatchErrorDump();
|
||||
int PS4_SYSV_ABI sceAjmBatchJobControlBufferRa();
|
||||
int PS4_SYSV_ABI sceAjmBatchJobInlineBuffer();
|
||||
int PS4_SYSV_ABI sceAjmBatchJobRunBufferRa();
|
||||
int PS4_SYSV_ABI sceAjmBatchJobRunSplitBufferRa();
|
||||
int PS4_SYSV_ABI sceAjmBatchStartBuffer();
|
||||
int PS4_SYSV_ABI sceAjmBatchWait();
|
||||
void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(AjmControlJob* batch_pos, u32 instance,
|
||||
AjmFlags flags, u8* in_buffer, u32 in_size,
|
||||
u8* out_buffer, u32 out_size, void* ret_addr);
|
||||
void* PS4_SYSV_ABI sceAjmBatchJobInlineBuffer(u8* batch_pos, const void* in_buffer, u64 in_size,
|
||||
const void** batch_address);
|
||||
void* PS4_SYSV_ABI sceAjmBatchJobRunBufferRa(AjmRunJob* batch_pos, u32 instance, AjmFlags flags,
|
||||
u8* in_buffer, u32 in_size, u8* out_buffer,
|
||||
const u32 out_size, u8* sideband_output,
|
||||
const u32 sideband_output_size, void* ret_addr);
|
||||
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, void* ret_addr);
|
||||
int PS4_SYSV_ABI sceAjmBatchStartBuffer(u32 context, const u8* batch, u32 batch_size,
|
||||
const int priority, AjmBatchError* batch_error,
|
||||
u32* out_batch_id);
|
||||
int PS4_SYSV_ABI sceAjmBatchWait(const u32 context, const u32 batch_id, const u32 timeout,
|
||||
AjmBatchError* const batch_error);
|
||||
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
|
||||
|
86
src/core/libraries/ajm/ajm_at9.cpp
Normal file
86
src/core/libraries/ajm/ajm_at9.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "common/assert.h"
|
||||
#include "core/libraries/ajm/ajm_at9.h"
|
||||
#include "error_codes.h"
|
||||
|
||||
extern "C" {
|
||||
#include <decoder.h>
|
||||
#include <libatrac9.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
}
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
AjmAt9Decoder::AjmAt9Decoder() {
|
||||
handle = Atrac9GetHandle();
|
||||
ASSERT_MSG(handle, "Atrac9GetHandle failed");
|
||||
AjmAt9Decoder::Reset();
|
||||
}
|
||||
|
||||
AjmAt9Decoder::~AjmAt9Decoder() {
|
||||
Atrac9ReleaseHandle(handle);
|
||||
}
|
||||
|
||||
void AjmAt9Decoder::Reset() {
|
||||
decoded_samples = 0;
|
||||
}
|
||||
|
||||
void AjmAt9Decoder::Initialize(const void* buffer, u32 buffer_size) {
|
||||
Atrac9ReleaseHandle(handle);
|
||||
handle = Atrac9GetHandle();
|
||||
ASSERT_MSG(buffer_size == sizeof(SceAjmDecAt9InitializeParameters),
|
||||
"Incorrect At9 initialization buffer size {}", buffer_size);
|
||||
const auto params = reinterpret_cast<const SceAjmDecAt9InitializeParameters*>(buffer);
|
||||
std::memcpy(config_data, params->config_data, SCE_AT9_CONFIG_DATA_SIZE);
|
||||
Atrac9InitDecoder(handle, config_data);
|
||||
}
|
||||
|
||||
void AjmAt9Decoder::GetCodecInfo(void* out_info) {
|
||||
Atrac9CodecInfo decoder_codec_info;
|
||||
Atrac9GetCodecInfo(handle, &decoder_codec_info);
|
||||
|
||||
auto* codec_info = reinterpret_cast<SceAjmSidebandDecAt9CodecInfo*>(out_info);
|
||||
codec_info->uiFrameSamples = decoder_codec_info.frameSamples;
|
||||
codec_info->uiFramesInSuperFrame = decoder_codec_info.framesInSuperframe;
|
||||
codec_info->uiNextFrameSize = static_cast<Atrac9Handle*>(handle)->Config.FrameBytes;
|
||||
codec_info->uiSuperFrameSize = decoder_codec_info.superframeSize;
|
||||
}
|
||||
|
||||
std::tuple<u32, u32, u32> AjmAt9Decoder::Decode(const u8* in_buf, u32 in_size, u8* out_buf,
|
||||
u32 out_size) {
|
||||
if (in_size <= 0 || out_size <= 0) {
|
||||
return std::tuple(in_size, out_size, 0);
|
||||
}
|
||||
|
||||
const auto decoder_handle = static_cast<Atrac9Handle*>(handle);
|
||||
Atrac9CodecInfo codec_info;
|
||||
Atrac9GetCodecInfo(handle, &codec_info);
|
||||
|
||||
int bytes_used = 0;
|
||||
int frame_count = 0;
|
||||
int bytes_remain = codec_info.superframeSize;
|
||||
|
||||
const auto written_size = codec_info.channels * codec_info.frameSamples * sizeof(u16);
|
||||
for (frame_count = 0;
|
||||
frame_count < decoder_handle->Config.FramesPerSuperframe && in_size > 0 && out_size > 0;
|
||||
frame_count++) {
|
||||
u32 ret = Atrac9Decode(decoder_handle, in_buf, (short*)out_buf, &bytes_used);
|
||||
ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret);
|
||||
in_buf += bytes_used;
|
||||
in_size -= bytes_used;
|
||||
bytes_remain -= bytes_used;
|
||||
out_buf += written_size;
|
||||
out_size -= written_size;
|
||||
decoded_samples += decoder_handle->Config.FrameSamples;
|
||||
}
|
||||
|
||||
in_size -= bytes_remain;
|
||||
|
||||
LOG_TRACE(Lib_Ajm, "Decoded {} samples, frame count: {}", decoded_samples, frame_count);
|
||||
return std::tuple(in_size, out_size, frame_count);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Ajm
|
53
src/core/libraries/ajm/ajm_at9.h
Normal file
53
src/core/libraries/ajm/ajm_at9.h
Normal file
@ -0,0 +1,53 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ajm/ajm_instance.h"
|
||||
|
||||
extern "C" {
|
||||
#include <structures.h>
|
||||
}
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
constexpr u32 SCE_AT9_CONFIG_DATA_SIZE = 4;
|
||||
constexpr s32 SCE_AJM_DEC_AT9_MAX_CHANNELS = 8;
|
||||
|
||||
struct SceAjmDecAt9InitializeParameters {
|
||||
u8 config_data[SCE_AT9_CONFIG_DATA_SIZE];
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
struct SceAjmSidebandDecAt9CodecInfo {
|
||||
u32 uiSuperFrameSize;
|
||||
u32 uiFramesInSuperFrame;
|
||||
u32 uiNextFrameSize;
|
||||
u32 uiFrameSamples;
|
||||
};
|
||||
|
||||
struct AjmAt9Decoder final : AjmInstance {
|
||||
void* handle;
|
||||
bool decoder_initialized = false;
|
||||
std::fstream file;
|
||||
int length;
|
||||
u8 config_data[SCE_AT9_CONFIG_DATA_SIZE];
|
||||
|
||||
explicit AjmAt9Decoder();
|
||||
~AjmAt9Decoder() override;
|
||||
|
||||
void Reset() override;
|
||||
|
||||
void Initialize(const void* buffer, u32 buffer_size) override;
|
||||
|
||||
void GetCodecInfo(void* out_info) override;
|
||||
|
||||
std::tuple<u32, u32, u32> Decode(const u8* in_buf, u32 in_size, u8* out_buf,
|
||||
u32 out_size) override;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ajm
|
77
src/core/libraries/ajm/ajm_instance.h
Normal file
77
src/core/libraries/ajm/ajm_instance.h
Normal file
@ -0,0 +1,77 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/enum.h"
|
||||
#include "common/types.h"
|
||||
|
||||
extern "C" {
|
||||
struct AVCodec;
|
||||
struct AVCodecContext;
|
||||
struct AVCodecParserContext;
|
||||
}
|
||||
|
||||
namespace Libraries::Ajm {
|
||||
|
||||
enum class AjmCodecType : u32 {
|
||||
Mp3Dec = 0,
|
||||
At9Dec = 1,
|
||||
M4aacDec = 2,
|
||||
Max = 23,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(AjmCodecType);
|
||||
static constexpr u32 NumAjmCodecs = u32(AjmCodecType::Max);
|
||||
|
||||
enum class AjmFormatEncoding : u32 {
|
||||
S16 = 0,
|
||||
S32 = 1,
|
||||
Float = 2,
|
||||
};
|
||||
|
||||
struct AjmSidebandResult {
|
||||
s32 result;
|
||||
s32 internal_result;
|
||||
};
|
||||
|
||||
struct AjmSidebandMFrame {
|
||||
u32 num_frames;
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
struct AjmSidebandStream {
|
||||
s32 input_consumed;
|
||||
s32 output_written;
|
||||
u64 total_decoded_samples;
|
||||
};
|
||||
|
||||
struct AjmSidebandFormat {
|
||||
u32 num_channels;
|
||||
u32 channel_mask;
|
||||
u32 sampl_freq;
|
||||
AjmFormatEncoding sample_encoding;
|
||||
u32 bitrate;
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
struct AjmInstance {
|
||||
AjmCodecType codec_type;
|
||||
u32 decoded_samples{};
|
||||
AjmFormatEncoding fmt{};
|
||||
u32 num_channels{};
|
||||
u32 index{};
|
||||
|
||||
explicit AjmInstance() = default;
|
||||
virtual ~AjmInstance() = default;
|
||||
|
||||
virtual void Reset() = 0;
|
||||
|
||||
virtual void Initialize(const void* buffer, u32 buffer_size) = 0;
|
||||
|
||||
virtual void GetCodecInfo(void* out_info) = 0;
|
||||
|
||||
virtual std::tuple<u32, u32, u32> Decode(const u8* in_buf, u32 in_size, u8* out_buf,
|
||||
u32 out_size) = 0;
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ajm
|
146
src/core/libraries/ajm/ajm_mp3.cpp
Normal file
146
src/core/libraries/ajm/ajm_mp3.cpp
Normal file
@ -0,0 +1,146 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.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};
|
||||
|
||||
SwrContext* swr_context{};
|
||||
|
||||
AVFrame* ConvertAudioFrame(AVFrame* frame) {
|
||||
auto pcm16_frame = av_frame_clone(frame);
|
||||
pcm16_frame->format = AV_SAMPLE_FMT_S16;
|
||||
|
||||
if (swr_context) {
|
||||
swr_free(&swr_context);
|
||||
swr_context = nullptr;
|
||||
}
|
||||
AVChannelLayout in_ch_layout = frame->ch_layout;
|
||||
AVChannelLayout out_ch_layout = pcm16_frame->ch_layout;
|
||||
swr_alloc_set_opts2(&swr_context, &out_ch_layout, AV_SAMPLE_FMT_S16, frame->sample_rate,
|
||||
&in_ch_layout, AVSampleFormat(frame->format), frame->sample_rate, 0,
|
||||
nullptr);
|
||||
swr_init(swr_context);
|
||||
const auto res = swr_convert_frame(swr_context, pcm16_frame, frame);
|
||||
if (res < 0) {
|
||||
LOG_ERROR(Lib_AvPlayer, "Could not convert to S16: {}", av_err2str(res));
|
||||
return nullptr;
|
||||
}
|
||||
av_frame_free(&frame);
|
||||
return pcm16_frame;
|
||||
}
|
||||
|
||||
AjmMp3Decoder::AjmMp3Decoder() {
|
||||
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");
|
||||
AjmMp3Decoder::Reset();
|
||||
}
|
||||
|
||||
AjmMp3Decoder::~AjmMp3Decoder() {
|
||||
avcodec_free_context(&c);
|
||||
av_free(c);
|
||||
}
|
||||
|
||||
void AjmMp3Decoder::Reset() {
|
||||
if (c) {
|
||||
avcodec_free_context(&c);
|
||||
av_free(c);
|
||||
}
|
||||
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");
|
||||
decoded_samples = 0;
|
||||
}
|
||||
|
||||
std::tuple<u32, u32, u32> AjmMp3Decoder::Decode(const u8* buf, u32 in_size, u8* out_buf,
|
||||
u32 out_size) {
|
||||
u32 num_frames = 0;
|
||||
AVPacket* pkt = av_packet_alloc();
|
||||
while (in_size > 0 && out_size > 0) {
|
||||
int ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size, buf, in_size, AV_NOPTS_VALUE,
|
||||
AV_NOPTS_VALUE, 0);
|
||||
ASSERT_MSG(ret >= 0, "Error while parsing {}", ret);
|
||||
buf += ret;
|
||||
in_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) {
|
||||
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);
|
||||
std::memcpy(out_buf, frame->data[0], size);
|
||||
file.write((const char*)frame->data[0], size);
|
||||
out_buf += size;
|
||||
out_size -= size;
|
||||
decoded_samples += frame->nb_samples;
|
||||
num_frames++;
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
av_packet_free(&pkt);
|
||||
return std::make_tuple(in_size, out_size, num_frames);
|
||||
}
|
||||
|
||||
int AjmMp3Decoder::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;
|
||||
}
|
||||
|
||||
} // namespace Libraries::Ajm
|
81
src/core/libraries/ajm/ajm_mp3.h
Normal file
81
src/core/libraries/ajm/ajm_mp3.h
Normal file
@ -0,0 +1,81 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fstream>
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ajm/ajm_instance.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 AjmDecMp3GetCodecInfoResult {
|
||||
AjmSidebandResult result;
|
||||
AjmSidebandDecMp3CodecInfo codec_info;
|
||||
};
|
||||
|
||||
struct AjmMp3Decoder : public AjmInstance {
|
||||
const AVCodec* codec = nullptr;
|
||||
AVCodecContext* c = 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 {}
|
||||
|
||||
std::tuple<u32, u32, u32> Decode(const u8* in_buf, u32 in_size, u8* out_buf,
|
||||
u32 out_size) override;
|
||||
|
||||
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
||||
AjmDecMp3ParseFrame* frame);
|
||||
};
|
||||
|
||||
} // namespace Libraries::Ajm
|
@ -1075,7 +1075,16 @@ ScePthread PThreadPool::Create(const char* name) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
auto* ret = new PthreadInternal{};
|
||||
#else
|
||||
// TODO: Linux specific hack
|
||||
static u8* hint_address = reinterpret_cast<u8*>(0x7FFFFC000ULL);
|
||||
auto* ret = reinterpret_cast<PthreadInternal*>(
|
||||
mmap(hint_address, sizeof(PthreadInternal), PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0));
|
||||
hint_address += Common::AlignUp(sizeof(PthreadInternal), 4_KB);
|
||||
#endif
|
||||
ret->is_free = false;
|
||||
ret->is_detached = false;
|
||||
ret->is_almost_done = false;
|
||||
|
Loading…
Reference in New Issue
Block a user