ajm: handle single-frame decode jobs (+mp3 imrovements)

This commit is contained in:
Vladislav Mikhalin 2024-11-11 20:59:33 +03:00
parent 7ab851592b
commit 5f63f46204
6 changed files with 366 additions and 91 deletions

View File

@ -40,23 +40,11 @@ void AjmAt9Decoder::Initialize(const void* buffer, u32 buffer_size) {
const auto params = reinterpret_cast<const AjmDecAt9InitializeParameters*>(buffer); const auto params = reinterpret_cast<const AjmDecAt9InitializeParameters*>(buffer);
std::memcpy(m_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 * GetPointCodeSize(), 0); m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels * GetPCMSize(m_format),
0);
} }
u8 AjmAt9Decoder::GetPointCodeSize() { void AjmAt9Decoder::GetInfo(void* out_info) const {
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) {
auto* info = reinterpret_cast<AjmSidebandDecAt9CodecInfo*>(out_info); auto* info = reinterpret_cast<AjmSidebandDecAt9CodecInfo*>(out_info);
info->super_frame_size = m_codec_info.superframeSize; info->super_frame_size = m_codec_info.superframeSize;
info->frames_in_super_frame = m_codec_info.framesInSuperframe; info->frames_in_super_frame = m_codec_info.framesInSuperframe;
@ -129,15 +117,20 @@ std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
return {1, samples_written / m_codec_info.channels}; return {1, samples_written / m_codec_info.channels};
} }
AjmSidebandFormat AjmAt9Decoder::GetFormat() { AjmSidebandFormat AjmAt9Decoder::GetFormat() const {
return AjmSidebandFormat{ return AjmSidebandFormat{
.num_channels = u32(m_codec_info.channels), .num_channels = u32(m_codec_info.channels),
.channel_mask = GetChannelMask(u32(m_codec_info.channels)), .channel_mask = GetChannelMask(u32(m_codec_info.channels)),
.sampl_freq = u32(m_codec_info.samplingRate), .sampl_freq = u32(m_codec_info.samplingRate),
.sample_encoding = m_format, .sample_encoding = m_format,
.bitrate = u32(m_codec_info.samplingRate * GetPointCodeSize() * 8), .bitrate = u32(m_codec_info.samplingRate * GetPCMSize(m_format) * 8),
.reserved = 0, .reserved = 0,
}; };
} }
u32 AjmAt9Decoder::GetNextFrameSize(u32 max_samples) const {
return std::min(u32(m_codec_info.frameSamples), max_samples) * m_codec_info.channels *
GetPCMSize(m_format);
}
} // namespace Libraries::Ajm } // namespace Libraries::Ajm

View File

@ -33,15 +33,14 @@ struct AjmAt9Decoder final : AjmCodec {
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 GetInfo(void* out_info) const override;
AjmSidebandFormat GetFormat() override; AjmSidebandFormat GetFormat() const override;
u32 GetNextFrameSize(u32 max_samples) const override;
std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output, std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmSidebandGaplessDecode& gapless, AjmSidebandGaplessDecode& gapless,
std::optional<u32> max_samples) override; std::optional<u32> max_samples) override;
private: private:
u8 GetPointCodeSize();
template <class T> template <class T>
size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_samples, u32 max_samples) { size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_samples, u32 max_samples) {
std::span<T> pcm_data{reinterpret_cast<T*>(m_pcm_buffer.data()), std::span<T> pcm_data{reinterpret_cast<T*>(m_pcm_buffer.data()),

View File

@ -22,6 +22,19 @@ 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;
u8 GetPCMSize(AjmFormatEncoding format) {
switch (format) {
case AjmFormatEncoding::S16:
return sizeof(s16);
case AjmFormatEncoding::S32:
return sizeof(s32);
case AjmFormatEncoding::Float:
return sizeof(float);
default:
UNREACHABLE();
}
}
AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) { AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_flags(flags) {
switch (codec_type) { switch (codec_type) {
case AjmCodecType::At9Dec: { case AjmCodecType::At9Dec: {
@ -30,7 +43,8 @@ AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_fl
break; break;
} }
case AjmCodecType::Mp3Dec: { case AjmCodecType::Mp3Dec: {
m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format)); m_codec = std::make_unique<AjmMp3Decoder>(AjmFormatEncoding(flags.format),
AjmMp3CodecFlags(flags.codec));
break; break;
} }
default: default:
@ -69,22 +83,29 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
} }
if (!job.input.buffer.empty() && !job.output.buffers.empty()) { if (!job.input.buffer.empty() && !job.output.buffers.empty()) {
u32 frames_decoded = 0;
std::span<u8> in_buf(job.input.buffer); std::span<u8> in_buf(job.input.buffer);
SparseOutputBuffer out_buf(job.output.buffers); SparseOutputBuffer out_buf(job.output.buffers);
u32 frames_decoded = 0;
auto in_size = in_buf.size(); auto in_size = in_buf.size();
auto out_size = out_buf.Size(); auto out_size = out_buf.Size();
while (!in_buf.empty() && !out_buf.IsEmpty() && !IsGaplessEnd()) { while (!in_buf.empty() && !out_buf.IsEmpty() && !IsGaplessEnd()) {
const auto samples_remain = const auto samples_remain = GetNumRemainingSamples();
m_gapless.total_samples != 0 if (!HasEnoughSpace(out_buf, samples_remain)) {
? std::optional<u32>{m_gapless.total_samples - m_gapless_samples} if (job.output.p_mframe == nullptr) {
: std::optional<u32>{}; LOG_ERROR(Lib_Ajm, "Single-frame job buffer too small.");
job.output.p_result->result = ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
}
break;
}
const auto [nframes, nsamples] = const auto [nframes, nsamples] =
m_codec->ProcessData(in_buf, out_buf, m_gapless, samples_remain); m_codec->ProcessData(in_buf, out_buf, m_gapless, samples_remain);
frames_decoded += nframes; frames_decoded += nframes;
m_total_samples += nsamples; m_total_samples += nsamples;
m_gapless_samples += nsamples; m_gapless_samples += nsamples;
if (job.output.p_mframe == nullptr) {
break;
}
} }
if (job.output.p_mframe) { if (job.output.p_mframe) {
job.output.p_mframe->num_frames = frames_decoded; job.output.p_mframe->num_frames = frames_decoded;
@ -113,8 +134,20 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
} }
} }
bool AjmInstance::IsGaplessEnd() { bool AjmInstance::IsGaplessEnd() const {
return m_gapless.total_samples != 0 && m_gapless_samples >= m_gapless.total_samples; return m_gapless.total_samples != 0 && m_gapless_samples >= m_gapless.total_samples;
} }
bool AjmInstance::HasEnoughSpace(const SparseOutputBuffer& output,
std::optional<u32> opt_samples_remain) const {
const auto remain = opt_samples_remain.value_or(std::numeric_limits<u32>::max());
return output.Size() >= m_codec->GetNextFrameSize(remain);
}
std::optional<u32> AjmInstance::GetNumRemainingSamples() const {
return m_gapless.total_samples != 0
? std::optional<u32>{m_gapless.total_samples - m_gapless_samples}
: std::optional<u32>{};
}
} // namespace Libraries::Ajm } // namespace Libraries::Ajm

View File

@ -14,6 +14,8 @@
namespace Libraries::Ajm { namespace Libraries::Ajm {
u8 GetPCMSize(AjmFormatEncoding format);
class SparseOutputBuffer { class SparseOutputBuffer {
public: public:
SparseOutputBuffer(std::span<std::span<u8>> chunks) SparseOutputBuffer(std::span<std::span<u8>> chunks)
@ -33,14 +35,17 @@ public:
++m_current; ++m_current;
} }
} }
if (!pcm.empty()) {
LOG_ERROR(Lib_Ajm, "Could not write {} samples", pcm.size());
}
return samples_written; return samples_written;
} }
bool IsEmpty() { bool IsEmpty() const {
return m_current == m_chunks.end(); return m_current == m_chunks.end();
} }
size_t Size() { size_t Size() const {
size_t result = 0; size_t result = 0;
for (auto it = m_current; it != m_chunks.end(); ++it) { for (auto it = m_current; it != m_chunks.end(); ++it) {
result += it->size(); result += it->size();
@ -59,8 +64,9 @@ public:
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 Reset() = 0;
virtual void GetInfo(void* out_info) = 0; virtual void GetInfo(void* out_info) const = 0;
virtual AjmSidebandFormat GetFormat() = 0; virtual AjmSidebandFormat GetFormat() const = 0;
virtual u32 GetNextFrameSize(u32 max_samples) const = 0;
virtual std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output, virtual std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmSidebandGaplessDecode& gapless, AjmSidebandGaplessDecode& gapless,
std::optional<u32> max_samples_per_channel) = 0; std::optional<u32> max_samples_per_channel) = 0;
@ -73,7 +79,9 @@ public:
void ExecuteJob(AjmJob& job); void ExecuteJob(AjmJob& job);
private: private:
bool IsGaplessEnd(); bool IsGaplessEnd() const;
bool HasEnoughSpace(const SparseOutputBuffer& output, std::optional<u32> samples_remain) const;
std::optional<u32> GetNumRemainingSamples() const;
AjmInstanceFlags m_flags{}; AjmInstanceFlags m_flags{};
AjmSidebandFormat m_format{}; AjmSidebandFormat m_format{};

View File

@ -15,18 +15,50 @@ extern "C" {
namespace Libraries::Ajm { namespace Libraries::Ajm {
// Following tables have been reversed from AJM library // Following tables have been reversed from AJM library
static constexpr std::array<std::array<s32, 3>, 3> SamplerateTable = {{ static constexpr std::array<std::array<s32, 4>, 4> Mp3SampleRateTable = {
{0x5622, 0x5DC0, 0x3E80}, std::array<s32, 4>{11025, 12000, 8000, 0},
{0xAC44, 0xBB80, 0x7D00}, std::array<s32, 4>{0, 0, 0, 0},
{0x2B11, 0x2EE0, 0x1F40}, std::array<s32, 4>{22050, 24000, 16000, 0},
}}; std::array<s32, 4>{44100, 48000, 32000, 0},
};
static constexpr std::array<std::array<s32, 15>, 2> BitrateTable = {{ static constexpr std::array<std::array<s32, 16>, 4> Mp3BitRateTable = {
{0, 0x20, 0x28, 0x30, 0x38, 0x40, 0x50, 0x60, 0x70, 0x80, 0xA0, 0xC0, 0xE0, 0x100, 0x140}, std::array<s32, 16>{0, 8, 16, 24, 32, 40, 48, 56, 64, 0, 0, 0, 0, 0, 0, 0},
{0, 0x8, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0}, std::array<s32, 16>{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
}}; std::array<s32, 16>{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0},
std::array<s32, 16>{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0},
};
static constexpr std::array<s32, 2> UnkTable = {0x48, 0x90}; enum class Mp3AudioVersion : u32 {
V2_5 = 0,
Reserved = 1,
V2 = 2,
V1 = 3,
};
enum class Mp3ChannelMode : u32 {
Stereo = 0,
JointStereo = 1,
DualChannel = 2,
SingleChannel = 3,
};
struct Mp3Header {
u32 emphasis : 2;
u32 original : 1;
u32 copyright : 1;
u32 mode_ext_idx : 2;
Mp3ChannelMode channel_mode : 2;
u32 : 1;
u32 padding : 1;
u32 sampling_rate_idx : 2;
u32 bitrate_idx : 4;
u32 protection_type : 1;
u32 layer_type : 2;
Mp3AudioVersion version : 2;
u32 sync : 11;
};
static_assert(sizeof(Mp3Header) == sizeof(u32));
static AVSampleFormat AjmToAVSampleFormat(AjmFormatEncoding format) { static AVSampleFormat AjmToAVSampleFormat(AjmFormatEncoding format) {
switch (format) { switch (format) {
@ -62,7 +94,7 @@ AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) {
swr_init(m_swr_context); swr_init(m_swr_context);
const auto res = swr_convert_frame(m_swr_context, new_frame, frame); const auto res = swr_convert_frame(m_swr_context, new_frame, frame);
if (res < 0) { if (res < 0) {
LOG_ERROR(Lib_AvPlayer, "Could not convert to S16: {}", av_err2str(res)); LOG_ERROR(Lib_AvPlayer, "Could not convert frame: {}", av_err2str(res));
av_frame_free(&new_frame); av_frame_free(&new_frame);
av_frame_free(&frame); av_frame_free(&frame);
return nullptr; return nullptr;
@ -71,29 +103,37 @@ AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) {
return new_frame; return new_frame;
} }
AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format) AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags)
: m_format(format), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)), : m_format(format), m_flags(flags), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)),
m_parser(av_parser_init(m_codec->id)) { m_codec_context(avcodec_alloc_context3(m_codec)), m_parser(av_parser_init(m_codec->id)) {
AjmMp3Decoder::Reset();
}
AjmMp3Decoder::~AjmMp3Decoder() {
swr_free(&m_swr_context);
avcodec_free_context(&m_codec_context);
}
void AjmMp3Decoder::Reset() {
if (m_codec_context) {
avcodec_free_context(&m_codec_context);
}
m_codec_context = avcodec_alloc_context3(m_codec);
ASSERT_MSG(m_codec_context, "Could not allocate audio m_codec context");
int ret = avcodec_open2(m_codec_context, m_codec, nullptr); int ret = avcodec_open2(m_codec_context, m_codec, nullptr);
ASSERT_MSG(ret >= 0, "Could not open m_codec"); ASSERT_MSG(ret >= 0, "Could not open m_codec");
} }
void AjmMp3Decoder::GetInfo(void* out_info) { AjmMp3Decoder::~AjmMp3Decoder() {
swr_free(&m_swr_context);
av_parser_close(m_parser);
avcodec_free_context(&m_codec_context);
}
void AjmMp3Decoder::Reset() {
avcodec_flush_buffers(m_codec_context);
m_header.reset();
m_frame_samples = 0;
}
void AjmMp3Decoder::GetInfo(void* out_info) const {
auto* info = reinterpret_cast<AjmSidebandDecMp3CodecInfo*>(out_info); auto* info = reinterpret_cast<AjmSidebandDecMp3CodecInfo*>(out_info);
if (m_header.has_value()) {
auto* header = reinterpret_cast<const Mp3Header*>(&m_header.value());
info->header = std::byteswap(m_header.value());
info->has_crc = header->protection_type;
info->channel_mode = static_cast<ChannelMode>(header->channel_mode);
info->mode_extension = header->mode_ext_idx;
info->copyright = header->copyright;
info->original = header->original;
info->emphasis = header->emphasis;
}
} }
std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output, std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
@ -101,6 +141,13 @@ std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
std::optional<u32> max_samples_per_channel) { std::optional<u32> max_samples_per_channel) {
AVPacket* pkt = av_packet_alloc(); AVPacket* pkt = av_packet_alloc();
if ((!m_header.has_value() || m_frame_samples == 0) && in_buf.size() >= 4) {
m_header = std::byteswap(*reinterpret_cast<u32*>(in_buf.data()));
AjmDecMp3ParseFrame info{};
ParseMp3Header(in_buf.data(), in_buf.size(), false, &info);
m_frame_samples = info.samples_per_channel;
}
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(), int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
in_buf.size(), 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);
@ -159,7 +206,9 @@ std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
UNREACHABLE(); UNREACHABLE();
} }
max_samples -= samples_decoded; if (max_samples_per_channel.has_value()) {
max_samples -= samples_decoded;
}
av_frame_free(&frame); av_frame_free(&frame);
} }
@ -167,38 +216,217 @@ std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
av_packet_free(&pkt); av_packet_free(&pkt);
return {frames_decoded, samples_decoded}; return {frames_decoded, samples_decoded / m_codec_context->ch_layout.nb_channels};
} }
int AjmMp3Decoder::ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl, u32 AjmMp3Decoder::GetNextFrameSize(u32 max_samples) const {
return std::min(m_frame_samples, max_samples) * m_codec_context->ch_layout.nb_channels *
GetPCMSize(m_format);
}
class BitReader {
public:
BitReader(const u8* data) : m_data(data) {}
template <class T>
T Read(u32 const nbits) {
T accumulator = 0;
for (unsigned i = 0; i < nbits; ++i) {
accumulator = (accumulator << 1) + GetBit();
}
return accumulator;
}
void Skip(size_t nbits) {
m_bit_offset += nbits;
}
size_t GetCurrentOffset() {
return m_bit_offset;
}
private:
u8 GetBit() {
const auto bit = (m_data[m_bit_offset / 8] >> (7 - (m_bit_offset % 8))) & 1;
m_bit_offset += 1;
return bit;
}
const u8* m_data;
size_t m_bit_offset = 0;
};
int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_ofl,
AjmDecMp3ParseFrame* frame) { AjmDecMp3ParseFrame* frame) {
LOG_INFO(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl); 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 (p_begin == nullptr || stream_size < 4 || frame == nullptr) {
}
if ((buf[0] & SYNCWORDH) != SYNCWORDH || (buf[1] & SYNCWORDL) != SYNCWORDL) {
return ORBIS_AJM_ERROR_INVALID_PARAMETER; return ORBIS_AJM_ERROR_INVALID_PARAMETER;
} }
const u32 unk_idx = buf[1] >> 3 & 1; const auto* p_current = p_begin;
const s32 version_idx = (buf[1] >> 3 & 3) ^ 2;
const s32 sr_idx = buf[2] >> 2 & 3; auto bytes = std::byteswap(*reinterpret_cast<const u32*>(p_current));
const s32 br_idx = (buf[2] >> 4) & 0xf; p_current += 4;
const s32 padding_bit = (buf[2] >> 1) & 0x1; auto header = reinterpret_cast<const Mp3Header*>(&bytes);
if (header->sync != 0x7FF) {
return ORBIS_AJM_ERROR_INVALID_PARAMETER;
}
frame->sample_rate = Mp3SampleRateTable[u32(header->version)][header->sampling_rate_idx];
frame->bitrate = Mp3BitRateTable[u32(header->version)][header->bitrate_idx] * 1000;
frame->num_channels = header->channel_mode == Mp3ChannelMode::SingleChannel ? 1 : 2;
if (header->version == Mp3AudioVersion::V1) {
frame->frame_size = (144 * frame->bitrate) / frame->sample_rate + header->padding;
frame->samples_per_channel = 1152;
} else {
frame->frame_size = (72 * frame->bitrate) / frame->sample_rate + header->padding;
frame->samples_per_channel = 576;
}
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; frame->encoder_delay = 0;
frame->num_frames = 0;
frame->total_samples = 0;
frame->ofl_type = AjmDecMp3OflType::None;
if (!parse_ofl) {
return ORBIS_OK;
}
BitReader reader(p_current);
if (header->protection_type == 0) {
reader.Skip(16); // crc = reader.Read<u16>(16);
}
if (header->version == Mp3AudioVersion::V1) {
// main_data_begin = reader.Read<u16>(9);
// if (header->channel_mode == Mp3ChannelMode::SingleChannel) {
// private_bits = reader.Read<u8>(5);
// } else {
// private_bits = reader.Read<u8>(3);
// }
// for (u32 ch = 0; ch < frame->num_channels; ++ch) {
// for (u8 band = 0; band < 4; ++band) {
// scfsi[ch][band] = reader.Read<bool>(1);
// }
// }
if (header->channel_mode == Mp3ChannelMode::SingleChannel) {
reader.Skip(18);
} else {
reader.Skip(20);
}
} else {
// main_data_begin = reader.Read<u16>(8);
// if (header->channel_mode == Mp3ChannelMode::SingleChannel) {
// private_bits = reader.Read<u8>(1);
// } else {
// private_bits = reader.Read<u8>(2);
// }
if (header->channel_mode == Mp3ChannelMode::SingleChannel) {
reader.Skip(9);
} else {
reader.Skip(10);
}
}
u32 part2_3_length = 0;
// Number of granules (18x32 sub-band samples)
const u8 ngr = header->version == Mp3AudioVersion::V1 ? 2 : 1;
for (u8 gr = 0; gr < ngr; ++gr) {
for (u32 ch = 0; ch < frame->num_channels; ++ch) {
// part2_3_length[gr][ch] = reader.Read<u16>(12);
part2_3_length += reader.Read<u16>(12);
// big_values[gr][ch] = reader.Read<u16>(9);
// global_main[gr][ch] = reader.Read<u8>(8);
// if (header->version == Mp3AudioVersion::V1) {
// scalefac_compress[gr][ch] = reader.Read<u16>(4);
// } else {
// scalefac_compress[gr][ch] = reader.Read<u16>(9);
// }
// window_switching_flag = reader.Read<bool>(1);
// if (window_switching_flag) {
// block_type[gr][ch] = reader.Read<u8>(2);
// mixed_block_flag[gr][ch] = reader.Read<bool>(1);
// for (u8 region = 0; region < 2; ++region) {
// table_select[gr][ch][region] = reader.Read<u8>(5);
// }
// for (u8 window = 0; window < 3; ++window) {
// subblock_gain[gr][ch][window] = reader.Read<u8>(3);
// }
// } else {
// for (u8 region = 0; region < 3; ++region) {
// table_select[gr][ch][region] = reader.Read<u8>(5);
// }
// region0_count[gr][ch] = reader.Read<u8>(4);
// region1_count[gr][ch] = reader.Read<u8>(3);
// }
// if (header->version == Mp3AudioVersion::V1) {
// preflag[gr][ch] = reader.Read<bool>(1);
// }
// scalefac_scale[gr][ch] = reader.Read<bool>(1);
// count1table_select[gr][ch] = reader.Read<bool>(1);
if (header->version == Mp3AudioVersion::V1) {
reader.Skip(47);
} else {
reader.Skip(51);
}
}
}
reader.Skip(part2_3_length);
p_current += ((reader.GetCurrentOffset() + 7) / 8);
const auto* p_end = p_begin + frame->frame_size;
if (memcmp(p_current, "Xing", 4) == 0 || memcmp(p_current, "Info", 4) == 0) {
// TODO: Parse Xing/Lame header
LOG_ERROR(Lib_Ajm, "Xing/Lame header is not implemented.");
} else if (memcmp(p_current, "VBRI", 4) == 0) {
// TODO: Parse VBRI header
LOG_ERROR(Lib_Ajm, "VBRI header is not implemented.");
} else {
// Parse FGH header
constexpr auto fgh_indicator = 0xB4;
while ((p_current + 9) < p_end && *p_current != fgh_indicator) {
++p_current;
}
auto p_fgh = p_current;
if ((p_current + 9) < p_end && *p_current == fgh_indicator) {
u8 crc = 0xFF;
auto crc_func = [](u8 c, u8 v, u8 s) {
if (((c >> 7) & 1) != ((v >> s) & 1)) {
return c * 2;
}
return (c * 2) ^ 0x45;
};
for (u8 i = 0; i < 9; ++i, ++p_current) {
for (u8 j = 0; j < 8; ++j) {
crc = crc_func(crc, *p_current, 7 - j);
}
}
if (p_fgh[9] == crc) {
frame->encoder_delay = std::byteswap(*reinterpret_cast<const u16*>(p_fgh + 1));
frame->total_samples = std::byteswap(*reinterpret_cast<const u32*>(p_fgh + 3));
frame->ofl_type = AjmDecMp3OflType::Fgh;
} else {
LOG_ERROR(Lib_Ajm, "FGH header CRC is incorrect.");
}
} else {
LOG_ERROR(Lib_Ajm, "Could not find vendor header.");
}
}
return ORBIS_OK; return ORBIS_OK;
} }
AjmSidebandFormat AjmMp3Decoder::GetFormat() { AjmSidebandFormat AjmMp3Decoder::GetFormat() const {
LOG_ERROR(Lib_Ajm, "Unimplemented"); return AjmSidebandFormat{
return AjmSidebandFormat{}; .num_channels = u32(m_codec_context->ch_layout.nb_channels),
.channel_mask = GetChannelMask(u32(m_codec_context->ch_layout.nb_channels)),
.sampl_freq = u32(m_codec_context->sample_rate),
.sample_encoding = m_format,
.bitrate = u32(m_codec_context->bit_rate),
.reserved = 0,
};
}; };
} // namespace Libraries::Ajm } // namespace Libraries::Ajm

View File

@ -13,7 +13,19 @@ struct SwrContext;
namespace Libraries::Ajm { namespace Libraries::Ajm {
enum class AjmDecMp3OflType : u32 { None = 0, Lame = 1, Vbri = 2, Fgh = 3, VbriAndFgh = 4 }; enum class AjmDecMp3OflType : u32 {
None = 0,
Lame = 1,
Vbri = 2,
Fgh = 3,
VbriAndFgh = 4,
};
enum AjmMp3CodecFlags : u32 {
IgnoreOfl = 1 << 0,
VlcRewind = 1 << 8,
};
DECLARE_ENUM_FLAG_OPERATORS(AjmMp3CodecFlags)
// 11-bit syncword if MPEG 2.5 extensions are enabled // 11-bit syncword if MPEG 2.5 extensions are enabled
static constexpr u8 SYNCWORDH = 0xff; static constexpr u8 SYNCWORDH = 0xff;
@ -51,13 +63,14 @@ struct AjmSidebandDecMp3CodecInfo {
class AjmMp3Decoder : public AjmCodec { class AjmMp3Decoder : public AjmCodec {
public: public:
explicit AjmMp3Decoder(AjmFormatEncoding format); explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags);
~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 GetInfo(void* out_info) const override;
AjmSidebandFormat GetFormat() override; AjmSidebandFormat GetFormat() const override;
u32 GetNextFrameSize(u32 max_samples) const override;
std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output, std::tuple<u32, u32> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmSidebandGaplessDecode& gapless, AjmSidebandGaplessDecode& gapless,
std::optional<u32> max_samples_per_channel) override; std::optional<u32> max_samples_per_channel) override;
@ -69,21 +82,22 @@ private:
template <class T> template <class T>
size_t WriteOutputSamples(AVFrame* frame, SparseOutputBuffer& output, u32 skipped_samples, size_t WriteOutputSamples(AVFrame* frame, SparseOutputBuffer& output, u32 skipped_samples,
u32 max_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]),
std::span<T> pcm_data(reinterpret_cast<T*>(frame->data[0]), size >> 1); frame->nb_samples * frame->ch_layout.nb_channels);
pcm_data = pcm_data.subspan(skipped_samples * frame->ch_layout.nb_channels); pcm_data = pcm_data.subspan(skipped_samples * frame->ch_layout.nb_channels);
const auto pcm_size = std::min(u32(pcm_data.size()), max_samples); return output.Write(pcm_data.subspan(0, 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); AVFrame* ConvertAudioFrame(AVFrame* frame);
const AjmFormatEncoding m_format; const AjmFormatEncoding m_format;
const AjmMp3CodecFlags m_flags;
const AVCodec* m_codec = nullptr; const AVCodec* m_codec = nullptr;
AVCodecContext* m_codec_context = nullptr; AVCodecContext* m_codec_context = nullptr;
AVCodecParserContext* m_parser = nullptr; AVCodecParserContext* m_parser = nullptr;
SwrContext* m_swr_context = nullptr; SwrContext* m_swr_context = nullptr;
std::optional<u32> m_header;
u32 m_frame_samples = 0;
}; };
} // namespace Libraries::Ajm } // namespace Libraries::Ajm