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

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

* disable breaking the loop in multi-frame if storage is insufficient

* simplified gapless decoding
This commit is contained in:
Vladislav Mikhalin 2024-11-14 13:01:20 +03:00 committed by GitHub
parent 8e281575b5
commit bf239ebc04
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 450 additions and 156 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;
@ -65,8 +53,7 @@ void AjmAt9Decoder::GetInfo(void* out_info) {
} }
std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output, std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
AjmSidebandGaplessDecode& gapless, AjmInstanceGapless& gapless) {
std::optional<u32> max_samples_per_channel) {
int ret = 0; int ret = 0;
int bytes_used = 0; int bytes_used = 0;
switch (m_format) { switch (m_format) {
@ -91,32 +78,37 @@ std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
m_superframe_bytes_remain -= bytes_used; m_superframe_bytes_remain -= bytes_used;
u32 skipped_samples = 0; u32 skip_samples = 0;
if (gapless.skipped_samples < gapless.skip_samples) { if (gapless.current.skip_samples > 0) {
skipped_samples = std::min(u32(m_codec_info.frameSamples), skip_samples = std::min(u16(m_codec_info.frameSamples), gapless.current.skip_samples);
u32(gapless.skip_samples - gapless.skipped_samples)); gapless.current.skip_samples -= skip_samples;
gapless.skipped_samples += skipped_samples;
} }
const auto max_samples = max_samples_per_channel.has_value() const auto max_pcm = gapless.init.total_samples != 0
? max_samples_per_channel.value() * m_codec_info.channels ? gapless.current.total_samples * m_codec_info.channels
: std::numeric_limits<u32>::max(); : std::numeric_limits<u32>::max();
size_t samples_written = 0; size_t pcm_written = 0;
switch (m_format) { switch (m_format) {
case AjmFormatEncoding::S16: case AjmFormatEncoding::S16:
samples_written = WriteOutputSamples<s16>(output, skipped_samples, max_samples); pcm_written = WriteOutputSamples<s16>(output, skip_samples, max_pcm);
break; break;
case AjmFormatEncoding::S32: case AjmFormatEncoding::S32:
samples_written = WriteOutputSamples<s32>(output, skipped_samples, max_samples); pcm_written = WriteOutputSamples<s32>(output, skip_samples, max_pcm);
break; break;
case AjmFormatEncoding::Float: case AjmFormatEncoding::Float:
samples_written = WriteOutputSamples<float>(output, skipped_samples, max_samples); pcm_written = WriteOutputSamples<float>(output, skip_samples, max_pcm);
break; break;
default: default:
UNREACHABLE(); UNREACHABLE();
} }
const auto samples_written = pcm_written / m_codec_info.channels;
gapless.current.skipped_samples += m_codec_info.frameSamples - samples_written;
if (gapless.init.total_samples != 0) {
gapless.current.total_samples -= samples_written;
}
m_num_frames += 1; m_num_frames += 1;
if ((m_num_frames % m_codec_info.framesInSuperframe) == 0) { if ((m_num_frames % m_codec_info.framesInSuperframe) == 0) {
if (m_superframe_bytes_remain) { if (m_superframe_bytes_remain) {
@ -126,18 +118,28 @@ std::tuple<u32, u32> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
m_num_frames = 0; m_num_frames = 0;
} }
return {1, samples_written / m_codec_info.channels}; return {1, samples_written};
} }
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 * m_codec_info.superframeSize * 8) /
(m_codec_info.framesInSuperframe * m_codec_info.frameSamples)),
.reserved = 0, .reserved = 0,
}; };
} }
u32 AjmAt9Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
const auto max_samples =
gapless.init.total_samples != 0
? std::min(gapless.current.total_samples, u32(m_codec_info.frameSamples))
: m_codec_info.frameSamples;
const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples);
return (max_samples - skip_samples) * m_codec_info.channels * GetPCMSize(m_format);
}
} // namespace Libraries::Ajm } // namespace Libraries::Ajm

View file

@ -33,15 +33,13 @@ 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(const AjmInstanceGapless& gapless) 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, AjmInstanceGapless& gapless) 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

@ -135,7 +135,10 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
case Identifier::AjmIdentInputControlBuf: { case Identifier::AjmIdentInputControlBuf: {
ASSERT_MSG(!input_control_buffer.has_value(), ASSERT_MSG(!input_control_buffer.has_value(),
"Only one instance of input control buffer is allowed per job"); "Only one instance of input control buffer is allowed per job");
input_control_buffer = batch_buffer.Consume<AjmChunkBuffer>(); const auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
if (buffer.p_address != nullptr && buffer.size != 0) {
input_control_buffer = buffer;
}
break; break;
} }
case Identifier::AjmIdentControlFlags: case Identifier::AjmIdentControlFlags:
@ -155,19 +158,27 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
case Identifier::AjmIdentInlineBuf: { case Identifier::AjmIdentInlineBuf: {
ASSERT_MSG(!output_control_buffer.has_value(), ASSERT_MSG(!output_control_buffer.has_value(),
"Only one instance of inline buffer is allowed per job"); "Only one instance of inline buffer is allowed per job");
inline_buffer = batch_buffer.Consume<AjmChunkBuffer>(); const auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
if (buffer.p_address != nullptr && buffer.size != 0) {
inline_buffer = buffer;
}
break; break;
} }
case Identifier::AjmIdentOutputRunBuf: { case Identifier::AjmIdentOutputRunBuf: {
auto& buffer = batch_buffer.Consume<AjmChunkBuffer>(); auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
u8* p_begin = reinterpret_cast<u8*>(buffer.p_address); u8* p_begin = reinterpret_cast<u8*>(buffer.p_address);
if (p_begin != nullptr && buffer.size != 0) {
job.output.buffers.emplace_back(std::span<u8>(p_begin, p_begin + buffer.size)); job.output.buffers.emplace_back(std::span<u8>(p_begin, p_begin + buffer.size));
}
break; break;
} }
case Identifier::AjmIdentOutputControlBuf: { case Identifier::AjmIdentOutputControlBuf: {
ASSERT_MSG(!output_control_buffer.has_value(), ASSERT_MSG(!output_control_buffer.has_value(),
"Only one instance of output control buffer is allowed per job"); "Only one instance of output control buffer is allowed per job");
output_control_buffer = batch_buffer.Consume<AjmChunkBuffer>(); const auto& buffer = batch_buffer.Consume<AjmChunkBuffer>();
if (buffer.p_address != nullptr && buffer.size != 0) {
output_control_buffer = buffer;
}
break; break;
} }
default: default:

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:
@ -45,7 +59,6 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
m_format = {}; m_format = {};
m_gapless = {}; m_gapless = {};
m_resample_parameters = {}; m_resample_parameters = {};
m_gapless_samples = 0;
m_total_samples = 0; m_total_samples = 0;
m_codec->Reset(); m_codec->Reset();
} }
@ -64,27 +77,47 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
} }
if (job.input.gapless_decode.has_value()) { if (job.input.gapless_decode.has_value()) {
auto& params = job.input.gapless_decode.value(); auto& params = job.input.gapless_decode.value();
m_gapless.total_samples = params.total_samples; if (params.total_samples != 0) {
m_gapless.skip_samples = params.skip_samples; const auto max = std::max(params.total_samples, m_gapless.init.total_samples);
m_gapless.current.total_samples += max - m_gapless.init.total_samples;
m_gapless.init.total_samples = max;
}
if (params.skip_samples != 0) {
const auto max = std::max(params.skip_samples, m_gapless.init.skip_samples);
m_gapless.current.skip_samples += max - m_gapless.init.skip_samples;
m_gapless.init.skip_samples = max;
}
} }
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() && !m_gapless.IsEnd()) {
const auto samples_remain = if (!HasEnoughSpace(out_buf)) {
m_gapless.total_samples != 0 if (job.output.p_mframe == nullptr || frames_decoded == 0) {
? std::optional<u32>{m_gapless.total_samples - m_gapless_samples} job.output.p_result->result = ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
: std::optional<u32>{}; break;
const auto [nframes, nsamples] = }
m_codec->ProcessData(in_buf, out_buf, m_gapless, samples_remain); }
const auto [nframes, nsamples] = m_codec->ProcessData(in_buf, out_buf, m_gapless);
frames_decoded += nframes; frames_decoded += nframes;
m_total_samples += nsamples; m_total_samples += nsamples;
m_gapless_samples += nsamples;
if (False(job.flags.run_flags & AjmJobRunFlags::MultipleFrames)) {
break;
}
}
if (m_gapless.IsEnd()) {
in_buf = in_buf.subspan(in_buf.size());
m_gapless.current.total_samples = m_gapless.init.total_samples;
m_gapless.current.skip_samples = m_gapless.init.skip_samples;
m_codec->Reset();
} }
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;
@ -96,25 +129,19 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
} }
} }
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_format != nullptr) { if (job.output.p_format != nullptr) {
*job.output.p_format = m_codec->GetFormat(); *job.output.p_format = m_codec->GetFormat();
} }
if (job.output.p_gapless_decode != nullptr) { if (job.output.p_gapless_decode != nullptr) {
*job.output.p_gapless_decode = m_gapless; *job.output.p_gapless_decode = m_gapless.current;
} }
if (job.output.p_codec_info != nullptr) { if (job.output.p_codec_info != nullptr) {
m_codec->GetInfo(job.output.p_codec_info); m_codec->GetInfo(job.output.p_codec_info);
} }
} }
bool AjmInstance::IsGaplessEnd() { bool AjmInstance::HasEnoughSpace(const SparseOutputBuffer& output) const {
return m_gapless.total_samples != 0 && m_gapless_samples >= m_gapless.total_samples; return output.Size() >= m_codec->GetNextFrameSize(m_gapless);
} }
} // 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();
@ -53,17 +58,26 @@ private:
std::span<std::span<u8>>::iterator m_current; std::span<std::span<u8>>::iterator m_current;
}; };
struct AjmInstanceGapless {
AjmSidebandGaplessDecode init{};
AjmSidebandGaplessDecode current{};
bool IsEnd() const {
return init.total_samples != 0 && current.total_samples == 0;
}
};
class AjmCodec { class AjmCodec {
public: public:
virtual ~AjmCodec() = default; virtual ~AjmCodec() = default;
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(const AjmInstanceGapless& gapless) 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, AjmInstanceGapless& gapless) = 0;
std::optional<u32> max_samples_per_channel) = 0;
}; };
class AjmInstance { class AjmInstance {
@ -73,16 +87,14 @@ public:
void ExecuteJob(AjmJob& job); void ExecuteJob(AjmJob& job);
private: private:
bool IsGaplessEnd(); bool HasEnoughSpace(const SparseOutputBuffer& output) const;
std::optional<u32> GetNumRemainingSamples() const;
AjmInstanceFlags m_flags{}; AjmInstanceFlags m_flags{};
AjmSidebandFormat m_format{}; AjmSidebandFormat m_format{};
AjmSidebandGaplessDecode m_gapless{}; AjmInstanceGapless m_gapless{};
AjmSidebandResampleParameters m_resample_parameters{}; AjmSidebandResampleParameters m_resample_parameters{};
u32 m_gapless_samples{};
u32 m_total_samples{}; u32 m_total_samples{};
std::unique_ptr<AjmCodec> m_codec; std::unique_ptr<AjmCodec> m_codec;
}; };

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,48 +103,57 @@ 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,
AjmSidebandGaplessDecode& gapless, AjmInstanceGapless& gapless) {
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);
in_buf = in_buf.subspan(ret); in_buf = in_buf.subspan(ret);
u32 frames_decoded = 0; u32 frames_decoded = 0;
u32 samples_decoded = 0; u32 samples_written = 0;
auto max_samples =
max_samples_per_channel.has_value()
? max_samples_per_channel.value() * m_codec_context->ch_layout.nb_channels
: std::numeric_limits<u32>::max();
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
@ -135,31 +176,38 @@ std::tuple<u32, u32> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOut
frame = ConvertAudioFrame(frame); frame = ConvertAudioFrame(frame);
frames_decoded += 1; frames_decoded += 1;
u32 skipped_samples = 0; u32 skip_samples = 0;
if (gapless.skipped_samples < gapless.skip_samples) { if (gapless.current.skip_samples > 0) {
skipped_samples = std::min(u32(frame->nb_samples), skip_samples = std::min(u16(frame->nb_samples), gapless.current.skip_samples);
u32(gapless.skip_samples - gapless.skipped_samples)); gapless.current.skip_samples -= skip_samples;
gapless.skipped_samples += skipped_samples;
} }
const auto max_pcm =
gapless.init.total_samples != 0
? gapless.current.total_samples * m_codec_context->ch_layout.nb_channels
: std::numeric_limits<u32>::max();
u32 pcm_written = 0;
switch (m_format) { switch (m_format) {
case AjmFormatEncoding::S16: case AjmFormatEncoding::S16:
samples_decoded += pcm_written = WriteOutputPCM<s16>(frame, output, skip_samples, max_pcm);
WriteOutputSamples<s16>(frame, output, skipped_samples, max_samples);
break; break;
case AjmFormatEncoding::S32: case AjmFormatEncoding::S32:
samples_decoded += pcm_written = WriteOutputPCM<s32>(frame, output, skip_samples, max_pcm);
WriteOutputSamples<s32>(frame, output, skipped_samples, max_samples);
break; break;
case AjmFormatEncoding::Float: case AjmFormatEncoding::Float:
samples_decoded += pcm_written = WriteOutputPCM<float>(frame, output, skip_samples, max_pcm);
WriteOutputSamples<float>(frame, output, skipped_samples, max_samples);
break; break;
default: default:
UNREACHABLE(); UNREACHABLE();
} }
max_samples -= samples_decoded; const auto samples = pcm_written / m_codec_context->ch_layout.nb_channels;
samples_written += samples;
gapless.current.skipped_samples += frame->nb_samples - samples;
if (gapless.init.total_samples != 0) {
gapless.current.total_samples -= samples;
}
av_frame_free(&frame); av_frame_free(&frame);
} }
@ -167,38 +215,221 @@ 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_written};
} }
int AjmMp3Decoder::ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl, u32 AjmMp3Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
const auto max_samples = gapless.init.total_samples != 0
? std::min(gapless.current.total_samples, m_frame_samples)
: m_frame_samples;
const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples);
return (max_samples - skip_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,39 +63,40 @@ 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(const AjmInstanceGapless& gapless) 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, AjmInstanceGapless& gapless) override;
std::optional<u32> max_samples_per_channel) 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);
private: private:
template <class T> template <class T>
size_t WriteOutputSamples(AVFrame* frame, SparseOutputBuffer& output, u32 skipped_samples, size_t WriteOutputPCM(AVFrame* frame, SparseOutputBuffer& output, u32 skipped_samples,
u32 max_samples) { u32 max_pcm) {
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_pcm)));
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