mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-08-04 16:32:39 +00:00
refactored most of ajm
This commit is contained in:
parent
b52632c924
commit
d1ba4c91b2
@ -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.cpp
|
||||||
src/core/libraries/ajm/ajm_context.h
|
src/core/libraries/ajm/ajm_context.h
|
||||||
src/core/libraries/ajm/ajm_error.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_instance.h
|
||||||
src/core/libraries/ajm/ajm_mp3.cpp
|
src/core/libraries/ajm/ajm_mp3.cpp
|
||||||
src/core/libraries/ajm/ajm_mp3.h
|
src/core/libraries/ajm/ajm_mp3.h
|
||||||
|
@ -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,
|
int PS4_SYSV_ABI sceAjmBatchWait(const u32 context_id, const u32 batch_id, const u32 timeout,
|
||||||
AjmBatchError* const batch_error) {
|
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);
|
timeout);
|
||||||
return context->BatchWait(batch_id, timeout, batch_error);
|
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,
|
int PS4_SYSV_ABI sceAjmDecMp3ParseFrame(const u8* buf, u32 stream_size, int parse_ofl,
|
||||||
AjmDecMp3ParseFrame* frame) {
|
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);
|
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) {
|
int PS4_SYSV_ABI sceAjmInitialize(s64 reserved, u32* p_context_id) {
|
||||||
LOG_INFO(Lib_Ajm, "called reserved = {}", reserved);
|
LOG_INFO(Lib_Ajm, "called reserved = {}", reserved);
|
||||||
|
ASSERT_MSG(context == nullptr, "Multiple contexts are currently unsupported.");
|
||||||
if (p_context_id == nullptr || reserved != 0) {
|
if (p_context_id == nullptr || reserved != 0) {
|
||||||
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,14 @@ namespace Libraries::Ajm {
|
|||||||
constexpr u32 ORBIS_AT9_CONFIG_DATA_SIZE = 4;
|
constexpr u32 ORBIS_AT9_CONFIG_DATA_SIZE = 4;
|
||||||
constexpr u32 AJM_INSTANCE_STATISTICS = 0x80000;
|
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 {
|
struct AjmBatchInfo {
|
||||||
void* pBuffer;
|
void* pBuffer;
|
||||||
u64 offset;
|
u64 offset;
|
||||||
@ -113,6 +121,11 @@ struct AjmDecAt9InitializeParameters {
|
|||||||
u32 reserved;
|
u32 reserved;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
union AjmSidebandInitParameters {
|
||||||
|
AjmDecAt9InitializeParameters at9;
|
||||||
|
u8 reserved[8];
|
||||||
|
};
|
||||||
|
|
||||||
union AjmInstanceFlags {
|
union AjmInstanceFlags {
|
||||||
u64 raw;
|
u64 raw;
|
||||||
struct {
|
struct {
|
||||||
@ -126,7 +139,6 @@ union AjmInstanceFlags {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct AjmDecMp3ParseFrame;
|
struct AjmDecMp3ParseFrame;
|
||||||
enum class AjmCodecType : u32;
|
|
||||||
|
|
||||||
int PS4_SYSV_ABI sceAjmBatchCancel(const u32 context_id, const u32 batch_id);
|
int PS4_SYSV_ABI sceAjmBatchCancel(const u32 context_id, const u32 batch_id);
|
||||||
int PS4_SYSV_ABI sceAjmBatchErrorDump();
|
int PS4_SYSV_ABI sceAjmBatchErrorDump();
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <fmt/format.h>
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "core/libraries/ajm/ajm_at9.h"
|
#include "core/libraries/ajm/ajm_at9.h"
|
||||||
#include "error_codes.h"
|
#include "error_codes.h"
|
||||||
@ -10,165 +8,83 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#include <decoder.h>
|
#include <decoder.h>
|
||||||
#include <libatrac9.h>
|
#include <libatrac9.h>
|
||||||
#include <libavutil/samplefmt.h>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Libraries::Ajm {
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
AjmAt9Decoder::AjmAt9Decoder() {
|
AjmAt9Decoder::AjmAt9Decoder() {
|
||||||
handle = Atrac9GetHandle();
|
m_handle = Atrac9GetHandle();
|
||||||
ASSERT_MSG(handle, "Atrac9GetHandle failed");
|
ASSERT_MSG(m_handle, "Atrac9GetHandle failed");
|
||||||
AjmAt9Decoder::Reset();
|
AjmAt9Decoder::Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
AjmAt9Decoder::~AjmAt9Decoder() {
|
AjmAt9Decoder::~AjmAt9Decoder() {
|
||||||
Atrac9ReleaseHandle(handle);
|
Atrac9ReleaseHandle(m_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AjmAt9Decoder::Reset() {
|
void AjmAt9Decoder::Reset() {
|
||||||
total_decoded_samples = 0;
|
Atrac9ReleaseHandle(m_handle);
|
||||||
gapless = {};
|
m_handle = Atrac9GetHandle();
|
||||||
|
Atrac9InitDecoder(m_handle, m_config_data);
|
||||||
|
Atrac9GetCodecInfo(m_handle, &m_codec_info);
|
||||||
|
|
||||||
ResetCodec();
|
m_num_frames = 0;
|
||||||
}
|
m_superframe_bytes_remain = m_codec_info.superframeSize;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AjmAt9Decoder::Initialize(const void* buffer, u32 buffer_size) {
|
void AjmAt9Decoder::Initialize(const void* buffer, u32 buffer_size) {
|
||||||
ASSERT_MSG(buffer_size == sizeof(AjmDecAt9InitializeParameters),
|
ASSERT_MSG(buffer_size == sizeof(AjmDecAt9InitializeParameters),
|
||||||
"Incorrect At9 initialization buffer size {}", buffer_size);
|
"Incorrect At9 initialization buffer size {}", buffer_size);
|
||||||
const auto params = reinterpret_cast<const AjmDecAt9InitializeParameters*>(buffer);
|
const auto params = reinterpret_cast<const AjmDecAt9InitializeParameters*>(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();
|
AjmAt9Decoder::Reset();
|
||||||
|
m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AjmAt9Decoder::GetCodecInfo(void* out_info) {
|
void AjmAt9Decoder::GetInfo(void* out_info) {
|
||||||
Atrac9CodecInfo decoder_codec_info;
|
auto* info = reinterpret_cast<AjmSidebandDecAt9CodecInfo*>(out_info);
|
||||||
Atrac9GetCodecInfo(handle, &decoder_codec_info);
|
info->super_frame_size = m_codec_info.superframeSize;
|
||||||
|
info->frames_in_super_frame = m_codec_info.framesInSuperframe;
|
||||||
auto* codec_info = reinterpret_cast<AjmSidebandDecAt9CodecInfo*>(out_info);
|
info->frame_samples = m_codec_info.frameSamples;
|
||||||
codec_info->uiFrameSamples = decoder_codec_info.frameSamples;
|
info->next_frame_size = static_cast<Atrac9Handle*>(m_handle)->Config.FrameBytes;
|
||||||
codec_info->uiFramesInSuperFrame = decoder_codec_info.framesInSuperframe;
|
|
||||||
codec_info->uiNextFrameSize = static_cast<Atrac9Handle*>(handle)->Config.FrameBytes;
|
|
||||||
codec_info->uiSuperFrameSize = decoder_codec_info.superframeSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AjmAt9Decoder::Decode(const AjmJob::Input* input, AjmJob::Output* output) {
|
u32 AjmAt9Decoder::ProcessFrame(std::span<u8>& in_buf, SparseOutputBuffer& output,
|
||||||
Atrac9CodecInfo codec_info;
|
AjmSidebandGaplessDecode& gapless, u32 max_samples_per_channel) {
|
||||||
Atrac9GetCodecInfo(handle, &codec_info);
|
|
||||||
|
|
||||||
size_t out_buffer_index = 0;
|
|
||||||
std::span<const u8> in_buf(input->buffer);
|
|
||||||
std::span<u8> 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto write_output = [&](std::span<s16> 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<s16> pcm_buffer(pcm_frame_size >> 1);
|
|
||||||
while (should_decode()) {
|
|
||||||
int bytes_used = 0;
|
int bytes_used = 0;
|
||||||
u32 ret = Atrac9Decode(handle, in_buf.data(), pcm_buffer.data(), &bytes_used);
|
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);
|
ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret);
|
||||||
in_buf = in_buf.subspan(bytes_used);
|
in_buf = in_buf.subspan(bytes_used);
|
||||||
superframe_bytes_remain -= bytes_used;
|
|
||||||
const size_t samples_remain =
|
m_superframe_bytes_remain -= bytes_used;
|
||||||
gapless.total_samples != 0
|
std::span<s16> pcm_data{m_pcm_buffer};
|
||||||
? (gapless.total_samples - gapless_decoded_samples) * codec_info.channels
|
|
||||||
: std::numeric_limits<size_t>::max();
|
|
||||||
bool written = false;
|
|
||||||
if (gapless.skipped_samples < gapless.skip_samples) {
|
if (gapless.skipped_samples < gapless.skip_samples) {
|
||||||
gapless.skipped_samples += codec_info.frameSamples;
|
const auto skipped_samples = std::min(u32(m_codec_info.frameSamples),
|
||||||
if (gapless.skipped_samples > gapless.skip_samples) {
|
u32(gapless.skip_samples - gapless.skipped_samples));
|
||||||
const u32 nsamples = gapless.skipped_samples - gapless.skip_samples;
|
gapless.skipped_samples += skipped_samples;
|
||||||
const auto start = codec_info.frameSamples - nsamples;
|
pcm_data = pcm_data.subspan(skipped_samples * m_codec_info.channels);
|
||||||
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;
|
const auto max_samples = max_samples_per_channel == std::numeric_limits<u32>::max()
|
||||||
if ((num_frames % codec_info.framesInSuperframe) == 0) {
|
? max_samples_per_channel
|
||||||
num_frames = 0;
|
: max_samples_per_channel * m_codec_info.channels;
|
||||||
if (superframe_bytes_remain) {
|
|
||||||
if (output->p_stream) {
|
const auto pcm_size = std::min(u32(pcm_data.size()), max_samples);
|
||||||
output->p_stream->input_consumed += superframe_bytes_remain;
|
const auto written = output.Write(pcm_data.subspan(0, pcm_size));
|
||||||
}
|
|
||||||
in_buf = in_buf.subspan(superframe_bytes_remain);
|
m_num_frames += 1;
|
||||||
}
|
if ((m_num_frames % m_codec_info.framesInSuperframe) == 0) {
|
||||||
superframe_bytes_remain = codec_info.superframeSize;
|
if (m_superframe_bytes_remain) {
|
||||||
num_superframes += 1;
|
in_buf = in_buf.subspan(m_superframe_bytes_remain);
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
m_superframe_bytes_remain = m_codec_info.superframeSize;
|
||||||
|
m_num_frames = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags.gapless_loop && gapless.total_samples != 0 &&
|
return (written / m_codec_info.channels) / sizeof(s16);
|
||||||
gapless_decoded_samples >= gapless.total_samples) {
|
|
||||||
ResetCodec();
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Libraries::Ajm
|
} // namespace Libraries::Ajm
|
||||||
|
@ -3,49 +3,39 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <fstream>
|
|
||||||
#include "common/assert.h"
|
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "core/libraries/ajm/ajm_instance.h"
|
#include "core/libraries/ajm/ajm_instance.h"
|
||||||
|
|
||||||
extern "C" {
|
#include "libatrac9.h"
|
||||||
#include <structures.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Libraries::Ajm {
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
constexpr s32 ORBIS_AJM_DEC_AT9_MAX_CHANNELS = 8;
|
constexpr s32 ORBIS_AJM_DEC_AT9_MAX_CHANNELS = 8;
|
||||||
|
|
||||||
struct AjmSidebandDecAt9CodecInfo {
|
struct AjmSidebandDecAt9CodecInfo {
|
||||||
u32 uiSuperFrameSize;
|
u32 super_frame_size;
|
||||||
u32 uiFramesInSuperFrame;
|
u32 frames_in_super_frame;
|
||||||
u32 uiNextFrameSize;
|
u32 next_frame_size;
|
||||||
u32 uiFrameSamples;
|
u32 frame_samples;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AjmAt9Decoder final : AjmInstance {
|
struct AjmAt9Decoder final : AjmCodec {
|
||||||
void* handle{};
|
|
||||||
u8 config_data[ORBIS_AT9_CONFIG_DATA_SIZE]{};
|
|
||||||
u32 superframe_bytes_remain{};
|
|
||||||
u32 num_frames{};
|
|
||||||
|
|
||||||
explicit AjmAt9Decoder();
|
explicit AjmAt9Decoder();
|
||||||
~AjmAt9Decoder() override;
|
~AjmAt9Decoder() override;
|
||||||
|
|
||||||
void Reset() override;
|
void Reset() override;
|
||||||
|
|
||||||
void Initialize(const void* buffer, u32 buffer_size) override;
|
void Initialize(const void* buffer, u32 buffer_size) override;
|
||||||
|
void GetInfo(void* out_info) override;
|
||||||
void GetCodecInfo(void* out_info) override;
|
u32 ProcessFrame(std::span<u8>& input, SparseOutputBuffer& output,
|
||||||
u32 GetCodecInfoSize() override {
|
AjmSidebandGaplessDecode& gapless, u32 max_samples) override;
|
||||||
return sizeof(AjmSidebandDecAt9CodecInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Decode(const AjmJob::Input* input, AjmJob::Output* output) override;
|
|
||||||
|
|
||||||
private:
|
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<s16> m_pcm_buffer;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Libraries::Ajm
|
} // namespace Libraries::Ajm
|
||||||
|
@ -46,18 +46,18 @@ static_assert(sizeof(AjmChunkBuffer) == 16);
|
|||||||
|
|
||||||
class AjmBatchBuffer {
|
class AjmBatchBuffer {
|
||||||
public:
|
public:
|
||||||
static constexpr size_t DynamicExtent = std::numeric_limits<size_t>::max();
|
static constexpr size_t s_dynamic_extent = std::numeric_limits<size_t>::max();
|
||||||
|
|
||||||
AjmBatchBuffer(u8* begin, u8* end)
|
AjmBatchBuffer(u8* begin, u8* end)
|
||||||
: m_p_begin(begin), m_p_current(begin), m_size(end - begin) {}
|
: 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) {}
|
: m_p_begin(begin), m_p_current(m_p_begin), m_size(size) {}
|
||||||
AjmBatchBuffer(std::span<u8> data)
|
AjmBatchBuffer(std::span<u8> data)
|
||||||
: m_p_begin(data.data()), m_p_current(m_p_begin), m_size(data.size()) {}
|
: 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;
|
auto current = m_p_current;
|
||||||
if (size != DynamicExtent) {
|
if (size != s_dynamic_extent) {
|
||||||
m_p_current += size;
|
m_p_current += size;
|
||||||
}
|
}
|
||||||
return AjmBatchBuffer(current, size);
|
return AjmBatchBuffer(current, size);
|
||||||
@ -65,7 +65,8 @@ public:
|
|||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
T& Peek() const {
|
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<T*>(m_p_current);
|
return *reinterpret_cast<T*>(m_p_current);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ public:
|
|||||||
T& Consume() {
|
T& Consume() {
|
||||||
auto* const result = reinterpret_cast<T*>(m_p_current);
|
auto* const result = reinterpret_cast<T*>(m_p_current);
|
||||||
m_p_current += sizeof(T);
|
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;
|
return *result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,11 +85,11 @@ public:
|
|||||||
|
|
||||||
void Advance(size_t size) {
|
void Advance(size_t size) {
|
||||||
m_p_current += 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() {
|
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 {
|
size_t BytesConsumed() const {
|
||||||
@ -96,8 +97,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t BytesRemaining() const {
|
size_t BytesRemaining() const {
|
||||||
if (m_size == DynamicExtent) {
|
if (m_size == s_dynamic_extent) {
|
||||||
return DynamicExtent;
|
return s_dynamic_extent;
|
||||||
}
|
}
|
||||||
return m_size - (m_p_current - m_p_begin);
|
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{};
|
*job.output.p_stream = AjmSidebandStream{};
|
||||||
}
|
}
|
||||||
if (True(sideband_flags & AjmJobSidebandFlags::Format) && !output_batch.IsEmpty()) {
|
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<AjmSidebandFormat>();
|
job.output.p_format = &output_batch.Consume<AjmSidebandFormat>();
|
||||||
*job.output.p_format = AjmSidebandFormat{};
|
*job.output.p_format = AjmSidebandFormat{};
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ struct AjmJob {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Output {
|
struct Output {
|
||||||
boost::container::small_vector<std::span<u8>, 4> buffers;
|
boost::container::small_vector<std::span<u8>, 8> buffers;
|
||||||
AjmSidebandResult* p_result = nullptr;
|
AjmSidebandResult* p_result = nullptr;
|
||||||
AjmSidebandStream* p_stream = nullptr;
|
AjmSidebandStream* p_stream = nullptr;
|
||||||
AjmSidebandFormat* p_format = nullptr;
|
AjmSidebandFormat* p_format = nullptr;
|
||||||
@ -48,7 +48,7 @@ struct AjmBatch {
|
|||||||
std::atomic_bool waiting{};
|
std::atomic_bool waiting{};
|
||||||
std::atomic_bool canceled{};
|
std::atomic_bool canceled{};
|
||||||
std::binary_semaphore finished{0};
|
std::binary_semaphore finished{0};
|
||||||
boost::container::small_vector<AjmJob, 8> jobs;
|
boost::container::small_vector<AjmJob, 16> jobs;
|
||||||
|
|
||||||
static std::shared_ptr<AjmBatch> FromBatchBuffer(std::span<u8> buffer);
|
static std::shared_ptr<AjmBatch> FromBatchBuffer(std::span<u8> buffer);
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/libraries/ajm/ajm.h"
|
||||||
#include "core/libraries/ajm/ajm_at9.h"
|
#include "core/libraries/ajm/ajm_at9.h"
|
||||||
#include "core/libraries/ajm/ajm_context.h"
|
#include "core/libraries/ajm/ajm_context.h"
|
||||||
#include "core/libraries/ajm/ajm_error.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<AjmJob> jobs) {
|
void AjmContext::ProcessBatch(u32 id, std::span<AjmJob> jobs) {
|
||||||
// Perform operation requested by control flags.
|
// Perform operation requested by control flags.
|
||||||
for (auto& job : jobs) {
|
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);
|
job.flags.raw);
|
||||||
|
|
||||||
std::shared_ptr<AjmInstance> instance;
|
std::shared_ptr<AjmInstance> instance;
|
||||||
@ -75,48 +76,7 @@ void AjmContext::ProcessBatch(u32 id, std::span<AjmJob> jobs) {
|
|||||||
instance = *p_instance;
|
instance = *p_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto control_flags = job.flags.control_flags;
|
instance->ExecuteJob(job);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,25 +150,10 @@ s32 AjmContext::InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags,
|
|||||||
return ORBIS_AJM_ERROR_CODEC_NOT_REGISTERED;
|
return ORBIS_AJM_ERROR_CODEC_NOT_REGISTERED;
|
||||||
}
|
}
|
||||||
ASSERT_MSG(flags.format == 0, "Only signed 16-bit PCM output is supported currently!");
|
ASSERT_MSG(flags.format == 0, "Only signed 16-bit PCM output is supported currently!");
|
||||||
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->codec_type = codec_type;
|
|
||||||
instance->num_channels = flags.channels;
|
|
||||||
instance->flags = flags;
|
|
||||||
|
|
||||||
std::optional<u32> opt_index;
|
std::optional<u32> opt_index;
|
||||||
{
|
{
|
||||||
std::unique_lock lock(instances_mutex);
|
std::unique_lock lock(instances_mutex);
|
||||||
opt_index = instances.Create(std::move(instance));
|
opt_index = instances.Create(std::move(std::make_unique<AjmInstance>(codec_type, flags)));
|
||||||
}
|
}
|
||||||
if (!opt_index.has_value()) {
|
if (!opt_index.has_value()) {
|
||||||
return ORBIS_AJM_ERROR_OUT_OF_RESOURCES;
|
return ORBIS_AJM_ERROR_OUT_OF_RESOURCES;
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace Libraries::Ajm {
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
static constexpr u32 MaxInstances = 0x2fff;
|
static constexpr u32 MaxInstances = 0x2fff;
|
||||||
static constexpr u32 MaxBatches = 0x0400;
|
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;
|
[[nodiscard]] bool IsRegistered(AjmCodecType type) const;
|
||||||
|
|
||||||
|
101
src/core/libraries/ajm/ajm_instance.cpp
Normal file
101
src/core/libraries/ajm/ajm_instance.cpp
Normal file
@ -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 <magic_enum.hpp>
|
||||||
|
|
||||||
|
namespace Libraries::Ajm {
|
||||||
|
|
||||||
|
AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) {
|
||||||
|
switch (codec_type) {
|
||||||
|
case AjmCodecType::At9Dec: {
|
||||||
|
m_codec = std::make_unique<AjmAt9Decoder>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AjmCodecType::Mp3Dec: {
|
||||||
|
m_codec = std::make_unique<AjmMp3Decoder>();
|
||||||
|
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<u8> 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<u32>::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
|
@ -8,17 +8,8 @@
|
|||||||
#include "core/libraries/ajm/ajm.h"
|
#include "core/libraries/ajm/ajm.h"
|
||||||
#include "core/libraries/ajm/ajm_batch.h"
|
#include "core/libraries/ajm/ajm_batch.h"
|
||||||
|
|
||||||
#include <boost/container/small_vector.hpp>
|
#include <memory>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <span>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
struct AVCodec;
|
|
||||||
struct AVCodecContext;
|
|
||||||
struct AVCodecParserContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Libraries::Ajm {
|
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_CODEC_ERROR = 0x40000000;
|
||||||
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
|
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
|
||||||
|
|
||||||
enum class AjmCodecType : u32 {
|
class SparseOutputBuffer {
|
||||||
Mp3Dec = 0,
|
public:
|
||||||
At9Dec = 1,
|
SparseOutputBuffer(std::span<std::span<u8>> chunks)
|
||||||
M4aacDec = 2,
|
: m_chunks(chunks), m_current(m_chunks.begin()) {}
|
||||||
Max = 23,
|
|
||||||
};
|
|
||||||
DECLARE_ENUM_FLAG_OPERATORS(AjmCodecType);
|
|
||||||
|
|
||||||
union AjmSidebandInitParameters {
|
template <class T>
|
||||||
AjmDecAt9InitializeParameters at9;
|
size_t Write(std::span<T> pcm) {
|
||||||
u8 reserved[8];
|
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<std::span<u8>> m_chunks;
|
||||||
|
std::span<std::span<u8>>::iterator m_current;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AjmInstance {
|
struct DecodeResult {
|
||||||
AjmCodecType codec_type;
|
u32 bytes_consumed{};
|
||||||
AjmFormatEncoding fmt{};
|
u32 bytes_written{};
|
||||||
AjmInstanceFlags flags{.raw = 0};
|
};
|
||||||
u32 num_channels{};
|
|
||||||
u32 index{};
|
|
||||||
u32 gapless_decoded_samples{};
|
|
||||||
u32 total_decoded_samples{};
|
|
||||||
AjmSidebandFormat format{};
|
|
||||||
AjmSidebandGaplessDecode gapless{};
|
|
||||||
AjmSidebandResampleParameters resample_parameters{};
|
|
||||||
|
|
||||||
explicit AjmInstance() = default;
|
class AjmCodec {
|
||||||
virtual ~AjmInstance() = default;
|
public:
|
||||||
|
virtual ~AjmCodec() = default;
|
||||||
virtual void Reset() = 0;
|
|
||||||
|
|
||||||
virtual void Initialize(const void* buffer, u32 buffer_size) = 0;
|
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<u8>& input, SparseOutputBuffer& output,
|
||||||
|
AjmSidebandGaplessDecode& gapless, u32 max_samples) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
virtual void GetCodecInfo(void* out_info) = 0;
|
class AjmInstance {
|
||||||
virtual u32 GetCodecInfoSize() = 0;
|
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<AjmCodec> m_codec;
|
||||||
|
|
||||||
|
// AjmCodecType codec_type;
|
||||||
|
// u32 index{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Libraries::Ajm
|
} // namespace Libraries::Ajm
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "core/libraries/ajm/ajm_error.h"
|
||||||
#include "core/libraries/ajm/ajm_mp3.h"
|
#include "core/libraries/ajm/ajm_mp3.h"
|
||||||
|
#include "core/libraries/error_codes.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <libavcodec/avcodec.h>
|
#include <libavcodec/avcodec.h>
|
||||||
@ -60,130 +62,140 @@ AjmMp3Decoder::AjmMp3Decoder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AjmMp3Decoder::~AjmMp3Decoder() {
|
AjmMp3Decoder::~AjmMp3Decoder() {
|
||||||
avcodec_free_context(&c);
|
avcodec_free_context(&codec_context);
|
||||||
av_free(c);
|
av_free(codec_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AjmMp3Decoder::Reset() {
|
void AjmMp3Decoder::Reset() {
|
||||||
if (c) {
|
if (codec_context) {
|
||||||
avcodec_free_context(&c);
|
avcodec_free_context(&codec_context);
|
||||||
av_free(c);
|
av_free(codec_context);
|
||||||
}
|
}
|
||||||
c = avcodec_alloc_context3(codec);
|
codec_context = avcodec_alloc_context3(codec);
|
||||||
ASSERT_MSG(c, "Could not allocate audio codec context");
|
ASSERT_MSG(codec_context, "Could not allocate audio codec context");
|
||||||
int ret = avcodec_open2(c, codec, nullptr);
|
int ret = avcodec_open2(codec_context, codec, nullptr);
|
||||||
ASSERT_MSG(ret >= 0, "Could not open codec");
|
ASSERT_MSG(ret >= 0, "Could not open codec");
|
||||||
total_decoded_samples = 0;
|
// total_decoded_samples = 0;
|
||||||
gapless_decoded_samples = 0;
|
// gapless_decoded_samples = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AjmMp3Decoder::Decode(const AjmJob::Input* input, AjmJob::Output* output) {
|
u32 AjmMp3Decoder::ProcessFrame(std::span<u8>& input, SparseOutputBuffer& output,
|
||||||
AVPacket* pkt = av_packet_alloc();
|
AjmSidebandGaplessDecode& gapless, u32 max_samples) {
|
||||||
|
// AVPacket* pkt = av_packet_alloc();
|
||||||
|
|
||||||
size_t out_buffer_index = 0;
|
// size_t out_buffer_index = 0;
|
||||||
std::span<const u8> in_buf(input->buffer);
|
// std::span<const u8> in_buf(input->buffer);
|
||||||
std::span<u8> out_buf = output->buffers[out_buffer_index];
|
// std::span<u8> out_buf = output->buffers[out_buffer_index];
|
||||||
const auto should_decode = [&] {
|
// const auto should_decode = [&] {
|
||||||
if (in_buf.empty() || out_buf.empty()) {
|
// if (in_buf.empty() || out_buf.empty()) {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
if (gapless.total_samples != 0 && gapless.total_samples < gapless_decoded_samples) {
|
// if (gapless.total_samples != 0 && gapless.total_samples < gapless_decoded_samples) {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
return true;
|
// return true;
|
||||||
};
|
// };
|
||||||
|
|
||||||
const auto write_output = [&](std::span<s16> pcm) {
|
// const auto write_output = [&](std::span<s16> pcm) {
|
||||||
while (!pcm.empty()) {
|
// while (!pcm.empty()) {
|
||||||
auto size = std::min(pcm.size() * sizeof(u16), out_buf.size());
|
// auto size = std::min(pcm.size() * sizeof(u16), out_buf.size());
|
||||||
std::memcpy(out_buf.data(), pcm.data(), size);
|
// std::memcpy(out_buf.data(), pcm.data(), size);
|
||||||
pcm = pcm.subspan(size >> 1);
|
// pcm = pcm.subspan(size >> 1);
|
||||||
out_buf = out_buf.subspan(size);
|
// out_buf = out_buf.subspan(size);
|
||||||
if (out_buf.empty()) {
|
// if (out_buf.empty()) {
|
||||||
out_buffer_index += 1;
|
// out_buffer_index += 1;
|
||||||
if (out_buffer_index >= output->buffers.size()) {
|
// if (out_buffer_index >= output->buffers.size()) {
|
||||||
return pcm.empty();
|
// return pcm.empty();
|
||||||
}
|
// }
|
||||||
out_buf = output->buffers[out_buffer_index];
|
// out_buf = output->buffers[out_buffer_index];
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return true;
|
// return true;
|
||||||
};
|
// };
|
||||||
|
|
||||||
while (should_decode()) {
|
// while (should_decode()) {
|
||||||
int ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size, in_buf.data(), in_buf.size(),
|
// int ret = av_parser_parse2(parser, codec_context, &pkt->data, &pkt->size, in_buf.data(),
|
||||||
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
|
// in_buf.size(), AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
|
||||||
ASSERT_MSG(ret >= 0, "Error while parsing {}", ret);
|
// ASSERT_MSG(ret >= 0, "Error while parsing {}", ret);
|
||||||
in_buf = in_buf.subspan(ret);
|
// in_buf = in_buf.subspan(ret);
|
||||||
|
|
||||||
if (output->p_stream) {
|
// if (output->p_stream) {
|
||||||
output->p_stream->input_consumed += ret;
|
// output->p_stream->input_consumed += ret;
|
||||||
}
|
// }
|
||||||
if (pkt->size) {
|
// if (pkt->size) {
|
||||||
// Send the packet with the compressed data to the decoder
|
// // Send the packet with the compressed data to the decoder
|
||||||
pkt->pts = parser->pts;
|
// pkt->pts = parser->pts;
|
||||||
pkt->dts = parser->dts;
|
// pkt->dts = parser->dts;
|
||||||
pkt->flags = (parser->key_frame == 1) ? AV_PKT_FLAG_KEY : 0;
|
// pkt->flags = (parser->key_frame == 1) ? AV_PKT_FLAG_KEY : 0;
|
||||||
ret = avcodec_send_packet(c, pkt);
|
// ret = avcodec_send_packet(codec_context, pkt);
|
||||||
ASSERT_MSG(ret >= 0, "Error submitting the packet to the decoder {}", ret);
|
// 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
|
// // Read all the output frames (in general there may be any number of them
|
||||||
while (ret >= 0) {
|
// while (ret >= 0) {
|
||||||
AVFrame* frame = av_frame_alloc();
|
// AVFrame* frame = av_frame_alloc();
|
||||||
ret = avcodec_receive_frame(c, frame);
|
// ret = avcodec_receive_frame(codec_context, frame);
|
||||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
// if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||||
break;
|
// break;
|
||||||
} else if (ret < 0) {
|
// } else if (ret < 0) {
|
||||||
UNREACHABLE_MSG("Error during decoding");
|
// UNREACHABLE_MSG("Error during decoding");
|
||||||
}
|
// }
|
||||||
if (frame->format != AV_SAMPLE_FMT_S16) {
|
// if (frame->format != AV_SAMPLE_FMT_S16) {
|
||||||
frame = ConvertAudioFrame(frame);
|
// frame = ConvertAudioFrame(frame);
|
||||||
}
|
// }
|
||||||
const auto size = frame->ch_layout.nb_channels * frame->nb_samples * sizeof(u16);
|
// const auto size = frame->ch_layout.nb_channels * frame->nb_samples * sizeof(u16);
|
||||||
if (gapless.skipped_samples < gapless.skip_samples) {
|
// if (gapless.skipped_samples < gapless.skip_samples) {
|
||||||
gapless.skipped_samples += frame->nb_samples;
|
// gapless.skipped_samples += frame->nb_samples;
|
||||||
if (gapless.skipped_samples > gapless.skip_samples) {
|
// if (gapless.skipped_samples > gapless.skip_samples) {
|
||||||
const u32 nsamples = gapless.skipped_samples - gapless.skip_samples;
|
// const u32 nsamples = gapless.skipped_samples - gapless.skip_samples;
|
||||||
const auto start = frame->nb_samples - nsamples;
|
// const auto start = frame->nb_samples - nsamples;
|
||||||
write_output({reinterpret_cast<s16*>(frame->data[0]),
|
// write_output({reinterpret_cast<s16*>(frame->data[0]),
|
||||||
nsamples * frame->ch_layout.nb_channels});
|
// nsamples * frame->ch_layout.nb_channels});
|
||||||
gapless.skipped_samples = gapless.skip_samples;
|
// gapless.skipped_samples = gapless.skip_samples;
|
||||||
total_decoded_samples += nsamples;
|
// total_decoded_samples += nsamples;
|
||||||
if (gapless.total_samples != 0) {
|
// if (gapless.total_samples != 0) {
|
||||||
gapless_decoded_samples += nsamples;
|
// gapless_decoded_samples += nsamples;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
write_output({reinterpret_cast<s16*>(frame->data[0]), size >> 1});
|
// write_output({reinterpret_cast<s16*>(frame->data[0]), size >> 1});
|
||||||
total_decoded_samples += frame->nb_samples;
|
// total_decoded_samples += frame->nb_samples;
|
||||||
if (gapless.total_samples != 0) {
|
// if (gapless.total_samples != 0) {
|
||||||
gapless_decoded_samples += frame->nb_samples;
|
// gapless_decoded_samples += frame->nb_samples;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
av_frame_free(&frame);
|
// av_frame_free(&frame);
|
||||||
if (output->p_stream) {
|
// if (output->p_stream) {
|
||||||
output->p_stream->output_written += size;
|
// output->p_stream->output_written += size;
|
||||||
}
|
// }
|
||||||
if (output->p_mframe) {
|
// if (output->p_mframe) {
|
||||||
output->p_mframe->num_frames += 1;
|
// output->p_mframe->num_frames += 1;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
av_packet_free(&pkt);
|
// av_packet_free(&pkt);
|
||||||
if (gapless.total_samples != 0 && gapless_decoded_samples >= gapless.total_samples) {
|
// if (gapless.total_samples != 0 && gapless_decoded_samples >= gapless.total_samples) {
|
||||||
if (flags.gapless_loop) {
|
// if (flags.gapless_loop) {
|
||||||
gapless.skipped_samples = 0;
|
// gapless.skipped_samples = 0;
|
||||||
gapless_decoded_samples = 0;
|
// gapless_decoded_samples = 0;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if (output->p_stream) {
|
// if (output->p_stream) {
|
||||||
output->p_stream->total_decoded_samples = total_decoded_samples;
|
// output->p_stream->total_decoded_samples = total_decoded_samples;
|
||||||
}
|
// }
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int AjmMp3Decoder::ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
int AjmMp3Decoder::ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
||||||
AjmDecMp3ParseFrame* frame) {
|
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 u32 unk_idx = buf[1] >> 3 & 1;
|
||||||
const s32 version_idx = (buf[1] >> 3 & 3) ^ 2;
|
const s32 version_idx = (buf[1] >> 3 & 3) ^ 2;
|
||||||
const s32 sr_idx = buf[2] >> 2 & 3;
|
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->frame_size = (UnkTable[unk_idx] * frame->bitrate) / frame->sample_rate + padding_bit;
|
||||||
frame->samples_per_channel = UnkTable[unk_idx] * 8;
|
frame->samples_per_channel = UnkTable[unk_idx] * 8;
|
||||||
frame->encoder_delay = 0;
|
frame->encoder_delay = 0;
|
||||||
if (parse_ofl == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Libraries::Ajm
|
} // namespace Libraries::Ajm
|
||||||
|
@ -56,25 +56,19 @@ struct AjmDecMp3GetCodecInfoResult {
|
|||||||
AjmSidebandDecMp3CodecInfo codec_info;
|
AjmSidebandDecMp3CodecInfo codec_info;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AjmMp3Decoder : public AjmInstance {
|
struct AjmMp3Decoder : public AjmCodec {
|
||||||
const AVCodec* codec = nullptr;
|
const AVCodec* codec = nullptr;
|
||||||
AVCodecContext* c = nullptr;
|
AVCodecContext* codec_context = nullptr;
|
||||||
AVCodecParserContext* parser = nullptr;
|
AVCodecParserContext* parser = nullptr;
|
||||||
std::ofstream file;
|
|
||||||
|
|
||||||
explicit AjmMp3Decoder();
|
explicit AjmMp3Decoder();
|
||||||
~AjmMp3Decoder() override;
|
~AjmMp3Decoder() override;
|
||||||
|
|
||||||
void Reset() override;
|
void Reset() override;
|
||||||
|
|
||||||
void Initialize(const void* buffer, u32 buffer_size) override {}
|
void Initialize(const void* buffer, u32 buffer_size) override {}
|
||||||
|
void GetInfo(void* out_info) override {}
|
||||||
void GetCodecInfo(void* out_info) override {}
|
u32 ProcessFrame(std::span<u8>& input, SparseOutputBuffer& output,
|
||||||
u32 GetCodecInfoSize() override {
|
AjmSidebandGaplessDecode& gapless, u32 max_samples) override;
|
||||||
return sizeof(AjmSidebandDecMp3CodecInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Decode(const AjmJob::Input* input, AjmJob::Output* output) override;
|
|
||||||
|
|
||||||
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
|
||||||
AjmDecMp3ParseFrame* frame);
|
AjmDecMp3ParseFrame* frame);
|
||||||
|
Loading…
Reference in New Issue
Block a user