AJM: Added some missing features (#1489)

* AJM: Added support for different PCM formats

* updated libatrac9 ref

* remove log

* Add support for non-interleaved flag

* Added support for output sideband format query
This commit is contained in:
Vladislav Mikhalin 2024-11-06 23:39:43 +03:00 committed by GitHub
parent f98b9f7726
commit 46ac48c311
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 226 additions and 81 deletions

2
externals/LibAtrac9 vendored

@ -1 +1 @@
Subproject commit 82767fe38823c32536726ea798f392b0b49e66b9
Subproject commit 3acdcdc78f129c2e6145331ff650fa76dd88d62c

View File

@ -13,8 +13,31 @@
namespace Libraries::Ajm {
constexpr int ORBIS_AJM_CHANNELMASK_MONO = 0x0004;
constexpr int ORBIS_AJM_CHANNELMASK_STEREO = 0x0003;
constexpr int ORBIS_AJM_CHANNELMASK_QUAD = 0x0033;
constexpr int ORBIS_AJM_CHANNELMASK_5POINT1 = 0x060F;
constexpr int ORBIS_AJM_CHANNELMASK_7POINT1 = 0x063F;
static std::unique_ptr<AjmContext> context{};
u32 GetChannelMask(u32 num_channels) {
switch (num_channels) {
case 1:
return ORBIS_AJM_CHANNELMASK_MONO;
case 2:
return ORBIS_AJM_CHANNELMASK_STEREO;
case 4:
return ORBIS_AJM_CHANNELMASK_QUAD;
case 6:
return ORBIS_AJM_CHANNELMASK_5POINT1;
case 8:
return ORBIS_AJM_CHANNELMASK_7POINT1;
default:
UNREACHABLE();
}
}
int PS4_SYSV_ABI sceAjmBatchCancel(const u32 context_id, const u32 batch_id) {
LOG_INFO(Lib_Ajm, "called context_id = {} batch_id = {}", context_id, batch_id);
return context->BatchCancel(batch_id);

View File

@ -137,9 +137,12 @@ union AjmInstanceFlags {
u64 codec : 28;
};
};
static_assert(sizeof(AjmInstanceFlags) == 8);
struct AjmDecMp3ParseFrame;
u32 GetChannelMask(u32 num_channels);
int PS4_SYSV_ABI sceAjmBatchCancel(const u32 context_id, const u32 batch_id);
int PS4_SYSV_ABI sceAjmBatchErrorDump();
void* PS4_SYSV_ABI sceAjmBatchJobControlBufferRa(void* p_buffer, u32 instance_id, u64 flags,

View File

@ -14,8 +14,8 @@ extern "C" {
namespace Libraries::Ajm {
AjmAt9Decoder::AjmAt9Decoder() {
m_handle = Atrac9GetHandle();
AjmAt9Decoder::AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags)
: m_format(format), m_flags(flags), m_handle(Atrac9GetHandle()) {
ASSERT_MSG(m_handle, "Atrac9GetHandle failed");
AjmAt9Decoder::Reset();
}
@ -40,7 +40,20 @@ void AjmAt9Decoder::Initialize(const void* buffer, u32 buffer_size) {
const auto params = reinterpret_cast<const AjmDecAt9InitializeParameters*>(buffer);
std::memcpy(m_config_data, params->config_data, ORBIS_AT9_CONFIG_DATA_SIZE);
AjmAt9Decoder::Reset();
m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels, 0);
m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels * GetPointCodeSize(), 0);
}
u8 AjmAt9Decoder::GetPointCodeSize() {
switch (m_format) {
case AjmFormatEncoding::S16:
return sizeof(s16);
case AjmFormatEncoding::S32:
return sizeof(s32);
case AjmFormatEncoding::Float:
return sizeof(float);
default:
UNREACHABLE();
}
}
void AjmAt9Decoder::GetInfo(void* out_info) {
@ -53,28 +66,56 @@ void AjmAt9Decoder::GetInfo(void* out_info) {
std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
AjmSidebandGaplessDecode& gapless,
u32 max_samples_per_channel) {
std::optional<u32> max_samples_per_channel) {
int ret = 0;
int bytes_used = 0;
u32 ret = Atrac9Decode(m_handle, in_buf.data(), m_pcm_buffer.data(), &bytes_used);
switch (m_format) {
case AjmFormatEncoding::S16:
ret = Atrac9Decode(m_handle, in_buf.data(), reinterpret_cast<s16*>(m_pcm_buffer.data()),
&bytes_used, True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
break;
case AjmFormatEncoding::S32:
ret = Atrac9DecodeS32(m_handle, in_buf.data(), reinterpret_cast<s32*>(m_pcm_buffer.data()),
&bytes_used, True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
break;
case AjmFormatEncoding::Float:
ret =
Atrac9DecodeF32(m_handle, in_buf.data(), reinterpret_cast<float*>(m_pcm_buffer.data()),
&bytes_used, True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
break;
default:
UNREACHABLE();
}
ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret);
in_buf = in_buf.subspan(bytes_used);
m_superframe_bytes_remain -= bytes_used;
std::span<s16> pcm_data{m_pcm_buffer};
u32 skipped_samples = 0;
if (gapless.skipped_samples < gapless.skip_samples) {
const auto skipped_samples = std::min(u32(m_codec_info.frameSamples),
skipped_samples = std::min(u32(m_codec_info.frameSamples),
u32(gapless.skip_samples - gapless.skipped_samples));
gapless.skipped_samples += skipped_samples;
pcm_data = pcm_data.subspan(skipped_samples * m_codec_info.channels);
}
const auto max_samples = max_samples_per_channel == std::numeric_limits<u32>::max()
? max_samples_per_channel
: max_samples_per_channel * m_codec_info.channels;
const auto max_samples = max_samples_per_channel.has_value()
? max_samples_per_channel.value() * m_codec_info.channels
: std::numeric_limits<u32>::max();
const auto pcm_size = std::min(u32(pcm_data.size()), max_samples);
const auto written = output.Write(pcm_data.subspan(0, pcm_size));
size_t samples_written = 0;
switch (m_format) {
case AjmFormatEncoding::S16:
samples_written = WriteOutputSamples<s16>(output, skipped_samples, max_samples);
break;
case AjmFormatEncoding::S32:
samples_written = WriteOutputSamples<s32>(output, skipped_samples, max_samples);
break;
case AjmFormatEncoding::Float:
samples_written = WriteOutputSamples<float>(output, skipped_samples, max_samples);
break;
default:
UNREACHABLE();
}
m_num_frames += 1;
if ((m_num_frames % m_codec_info.framesInSuperframe) == 0) {
@ -85,7 +126,18 @@ std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
m_num_frames = 0;
}
return {1, (written / m_codec_info.channels) / sizeof(s16)};
return {1, samples_written / m_codec_info.channels};
}
AjmSidebandFormat AjmAt9Decoder::GetFormat() {
return AjmSidebandFormat{
.num_channels = u32(m_codec_info.channels),
.channel_mask = GetChannelMask(u32(m_codec_info.channels)),
.sampl_freq = u32(m_codec_info.samplingRate),
.sample_encoding = m_format,
.bitrate = u32(m_codec_info.samplingRate * GetPointCodeSize() * 8),
.reserved = 0,
};
}
} // namespace Libraries::Ajm

View File

@ -8,10 +8,18 @@
#include "libatrac9.h"
#include <span>
namespace Libraries::Ajm {
constexpr s32 ORBIS_AJM_DEC_AT9_MAX_CHANNELS = 8;
enum AjmAt9CodecFlags : u32 {
ParseRiffHeader = 1 << 0,
NonInterleavedOutput = 1 << 8,
};
DECLARE_ENUM_FLAG_OPERATORS(AjmAt9CodecFlags)
struct AjmSidebandDecAt9CodecInfo {
u32 super_frame_size;
u32 frames_in_super_frame;
@ -20,22 +28,37 @@ struct AjmSidebandDecAt9CodecInfo {
};
struct AjmAt9Decoder final : AjmCodec {
explicit AjmAt9Decoder();
explicit AjmAt9Decoder(AjmFormatEncoding format, AjmAt9CodecFlags flags);
~AjmAt9Decoder() override;
void Reset() override;
void Initialize(const void* buffer, u32 buffer_size) override;
void GetInfo(void* out_info) override;
AjmSidebandFormat GetFormat() override;
std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmSidebandGaplessDecode& gapless, u32 max_samples) override;
AjmSidebandGaplessDecode& gapless,
std::optional<u32> max_samples) override;
private:
u8 GetPointCodeSize();
template <class T>
size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_samples, u32 max_samples) {
std::span<T> pcm_data{reinterpret_cast<T*>(m_pcm_buffer.data()),
m_pcm_buffer.size() / sizeof(T)};
pcm_data = pcm_data.subspan(skipped_samples * m_codec_info.channels);
const auto pcm_size = std::min(u32(pcm_data.size()), max_samples);
return output.Write(pcm_data.subspan(0, pcm_size));
}
const AjmFormatEncoding m_format;
const AjmAt9CodecFlags m_flags;
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;
std::vector<u8> m_pcm_buffer;
};
} // namespace Libraries::Ajm

View File

@ -149,7 +149,6 @@ s32 AjmContext::InstanceCreate(AjmCodecType codec_type, AjmInstanceFlags flags,
if (!IsRegistered(codec_type)) {
return ORBIS_AJM_ERROR_CODEC_NOT_REGISTERED;
}
ASSERT_MSG(flags.format == 0, "Only signed 16-bit PCM output is supported currently!");
std::optional<u32> opt_index;
{
std::unique_lock lock(instances_mutex);

View File

@ -9,14 +9,28 @@
namespace Libraries::Ajm {
constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) {
switch (codec_type) {
case AjmCodecType::At9Dec: {
m_codec = std::make_unique<AjmAt9Decoder>();
m_codec = std::make_unique<AjmAt9Decoder>(AjmFormatEncoding(flags.format),
AjmAt9CodecFlags(flags.codec));
break;
}
case AjmCodecType::Mp3Dec: {
m_codec = std::make_unique<AjmMp3Decoder>();
m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format));
break;
}
default:
@ -62,9 +76,10 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
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 samples_remain =
m_gapless.total_samples != 0
? std::optional<u32>{m_gapless.total_samples - m_gapless_samples}
: std::optional<u32>{};
const auto [nframes, nsamples] =
m_codec->ProcessData(in_buf, out_buf, m_gapless, samples_remain);
frames_decoded += nframes;
@ -87,6 +102,9 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
m_gapless.skipped_samples = 0;
m_codec->Reset();
}
if (job.output.p_format != nullptr) {
*job.output.p_format = m_codec->GetFormat();
}
if (job.output.p_gapless_decode != nullptr) {
*job.output.p_gapless_decode = m_gapless;
}

View File

@ -14,19 +14,6 @@
namespace Libraries::Ajm {
constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
class SparseOutputBuffer {
public:
SparseOutputBuffer(std::span<std::span<u8>> chunks)
@ -34,18 +21,19 @@ public:
template <class T>
size_t Write(std::span<T> pcm) {
size_t bytes_written = 0;
size_t samples_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));
const auto nsamples = size / sizeof(T);
samples_written += nsamples;
pcm = pcm.subspan(nsamples);
*m_current = m_current->subspan(size);
if (m_current->empty()) {
++m_current;
}
}
return bytes_written;
return samples_written;
}
bool IsEmpty() {
@ -65,11 +53,6 @@ private:
std::span<std::span<u8>>::iterator m_current;
};
struct DecodeResult {
u32 bytes_consumed{};
u32 bytes_written{};
};
class AjmCodec {
public:
virtual ~AjmCodec() = default;
@ -77,9 +60,10 @@ public:
virtual void Initialize(const void* buffer, u32 buffer_size) = 0;
virtual void Reset() = 0;
virtual void GetInfo(void* out_info) = 0;
virtual AjmSidebandFormat GetFormat() = 0;
virtual std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmSidebandGaplessDecode& gapless,
u32 max_samples) = 0;
std::optional<u32> max_samples_per_channel) = 0;
};
class AjmInstance {
@ -100,9 +84,6 @@ private:
u32 m_total_samples{};
std::unique_ptr<AjmCodec> m_codec;
// AjmCodecType codec_type;
// u32 index{};
};
} // namespace Libraries::Ajm

View File

@ -30,9 +30,27 @@ static constexpr std::array<s32, 2> UnkTable = {0x48, 0x90};
SwrContext* swr_context{};
AVFrame* ConvertAudioFrame(AVFrame* frame) {
static AVSampleFormat AjmToAVSampleFormat(AjmFormatEncoding format) {
switch (format) {
case AjmFormatEncoding::S16:
return AV_SAMPLE_FMT_S16;
case AjmFormatEncoding::S32:
return AV_SAMPLE_FMT_S32;
case AjmFormatEncoding::Float:
return AV_SAMPLE_FMT_FLT;
default:
UNREACHABLE();
}
}
AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) {
AVSampleFormat format = AjmToAVSampleFormat(m_format);
if (frame->format == format) {
return frame;
}
auto pcm16_frame = av_frame_clone(frame);
pcm16_frame->format = AV_SAMPLE_FMT_S16;
pcm16_frame->format = format;
if (swr_context) {
swr_free(&swr_context);
@ -40,9 +58,9 @@ AVFrame* ConvertAudioFrame(AVFrame* frame) {
}
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_alloc_set_opts2(&swr_context, &out_ch_layout, AVSampleFormat(pcm16_frame->format),
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) {
@ -53,11 +71,9 @@ AVFrame* ConvertAudioFrame(AVFrame* frame) {
return pcm16_frame;
}
AjmMp3Decoder::AjmMp3Decoder() {
m_codec = avcodec_find_decoder(AV_CODEC_ID_MP3);
ASSERT_MSG(m_codec, "MP3 m_codec not found");
m_parser = av_parser_init(m_codec->id);
ASSERT_MSG(m_parser, "Parser not found");
AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format)
: m_format(format), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)),
m_parser(av_parser_init(m_codec->id)) {
AjmMp3Decoder::Reset();
}
@ -81,7 +97,7 @@ void AjmMp3Decoder::GetInfo(void* out_info) {
std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
AjmSidebandGaplessDecode& gapless,
u32 max_samples) {
std::optional<u32> max_samples_per_channel) {
AVPacket* pkt = av_packet_alloc();
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
@ -109,24 +125,37 @@ std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
} else if (ret < 0) {
UNREACHABLE_MSG("Error during decoding");
}
if (frame->format != AV_SAMPLE_FMT_S16) {
frame = ConvertAudioFrame(frame);
}
frames_decoded += 1;
samples_decoded += frame->nb_samples;
const auto size = frame->ch_layout.nb_channels * frame->nb_samples * sizeof(u16);
std::span<s16> pcm_data(reinterpret_cast<s16*>(frame->data[0]), size >> 1);
u32 skipped_samples = 0;
if (gapless.skipped_samples < gapless.skip_samples) {
const auto skipped_samples = std::min(
u32(frame->nb_samples), u32(gapless.skip_samples - gapless.skipped_samples));
skipped_samples = std::min(u32(frame->nb_samples),
u32(gapless.skip_samples - gapless.skipped_samples));
gapless.skipped_samples += skipped_samples;
pcm_data = pcm_data.subspan(skipped_samples * frame->ch_layout.nb_channels);
samples_decoded -= skipped_samples;
}
const auto pcm_size = std::min(u32(pcm_data.size()), max_samples);
output.Write(pcm_data.subspan(0, pcm_size));
const auto max_samples =
max_samples_per_channel.has_value()
? max_samples_per_channel.value() * frame->ch_layout.nb_channels
: std::numeric_limits<u32>::max();
switch (m_format) {
case AjmFormatEncoding::S16:
samples_decoded +=
WriteOutputSamples<s16>(frame, output, skipped_samples, max_samples);
break;
case AjmFormatEncoding::S32:
samples_decoded +=
WriteOutputSamples<s32>(frame, output, skipped_samples, max_samples);
break;
case AjmFormatEncoding::Float:
samples_decoded +=
WriteOutputSamples<float>(frame, output, skipped_samples, max_samples);
break;
default:
UNREACHABLE();
}
av_frame_free(&frame);
}
@ -163,4 +192,9 @@ int AjmMp3Decoder::ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
return ORBIS_OK;
}
AjmSidebandFormat AjmMp3Decoder::GetFormat() {
LOG_ERROR(Lib_Ajm, "Unimplemented");
return AjmSidebandFormat{};
};
} // namespace Libraries::Ajm

View File

@ -7,11 +7,7 @@
#include "core/libraries/ajm/ajm_instance.h"
extern "C" {
struct AVCodec;
struct AVCodecContext;
struct AVCodecParserContext;
struct AVFrame;
struct AVPacket;
#include <libavcodec/avcodec.h>
}
namespace Libraries::Ajm {
@ -54,19 +50,35 @@ struct AjmSidebandDecMp3CodecInfo {
class AjmMp3Decoder : public AjmCodec {
public:
explicit AjmMp3Decoder();
explicit AjmMp3Decoder(AjmFormatEncoding format);
~AjmMp3Decoder() override;
void Reset() override;
void Initialize(const void* buffer, u32 buffer_size) override {}
void GetInfo(void* out_info) override;
AjmSidebandFormat GetFormat() override;
std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmSidebandGaplessDecode& gapless, u32 max_samples) override;
AjmSidebandGaplessDecode& gapless,
std::optional<u32> max_samples_per_channel) override;
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
AjmDecMp3ParseFrame* frame);
private:
template <class T>
size_t WriteOutputSamples(AVFrame* frame, SparseOutputBuffer& output, u32 skipped_samples,
u32 max_samples) {
const auto size = frame->ch_layout.nb_channels * frame->nb_samples * sizeof(T);
std::span<T> pcm_data(reinterpret_cast<T*>(frame->data[0]), size >> 1);
pcm_data = pcm_data.subspan(skipped_samples * frame->ch_layout.nb_channels);
const auto pcm_size = std::min(u32(pcm_data.size()), max_samples);
const auto samples_written = output.Write(pcm_data.subspan(0, pcm_size));
return samples_written / frame->ch_layout.nb_channels;
}
AVFrame* ConvertAudioFrame(AVFrame* frame);
const AjmFormatEncoding m_format;
const AVCodec* m_codec = nullptr;
AVCodecContext* m_codec_context = nullptr;
AVCodecParserContext* m_parser = nullptr;