From bf239ebc04c2c2b7240608101193deda130acc5b Mon Sep 17 00:00:00 2001 From: Vladislav Mikhalin Date: Thu, 14 Nov 2024 13:01:20 +0300 Subject: [PATCH] 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 --- src/core/libraries/ajm/ajm_at9.cpp | 66 ++--- src/core/libraries/ajm/ajm_at9.h | 10 +- src/core/libraries/ajm/ajm_batch.cpp | 19 +- src/core/libraries/ajm/ajm_instance.cpp | 71 +++-- src/core/libraries/ajm/ajm_instance.h | 34 ++- src/core/libraries/ajm/ajm_mp3.cpp | 367 +++++++++++++++++++----- src/core/libraries/ajm/ajm_mp3.h | 39 ++- 7 files changed, 450 insertions(+), 156 deletions(-) diff --git a/src/core/libraries/ajm/ajm_at9.cpp b/src/core/libraries/ajm/ajm_at9.cpp index 7fff0ff0..936929ae 100644 --- a/src/core/libraries/ajm/ajm_at9.cpp +++ b/src/core/libraries/ajm/ajm_at9.cpp @@ -40,23 +40,11 @@ void AjmAt9Decoder::Initialize(const void* buffer, u32 buffer_size) { const auto params = reinterpret_cast(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 * GetPointCodeSize(), 0); + m_pcm_buffer.resize(m_codec_info.frameSamples * m_codec_info.channels * GetPCMSize(m_format), + 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) { +void AjmAt9Decoder::GetInfo(void* out_info) const { auto* info = reinterpret_cast(out_info); info->super_frame_size = m_codec_info.superframeSize; info->frames_in_super_frame = m_codec_info.framesInSuperframe; @@ -65,8 +53,7 @@ void AjmAt9Decoder::GetInfo(void* out_info) { } std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf, SparseOutputBuffer& output, - AjmSidebandGaplessDecode& gapless, - std::optional max_samples_per_channel) { + AjmInstanceGapless& gapless) { int ret = 0; int bytes_used = 0; switch (m_format) { @@ -91,32 +78,37 @@ std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf, SparseOut m_superframe_bytes_remain -= bytes_used; - u32 skipped_samples = 0; - if (gapless.skipped_samples < gapless.skip_samples) { - skipped_samples = std::min(u32(m_codec_info.frameSamples), - u32(gapless.skip_samples - gapless.skipped_samples)); - gapless.skipped_samples += skipped_samples; + u32 skip_samples = 0; + if (gapless.current.skip_samples > 0) { + skip_samples = std::min(u16(m_codec_info.frameSamples), gapless.current.skip_samples); + gapless.current.skip_samples -= skip_samples; } - const auto max_samples = max_samples_per_channel.has_value() - ? max_samples_per_channel.value() * m_codec_info.channels - : std::numeric_limits::max(); + const auto max_pcm = gapless.init.total_samples != 0 + ? gapless.current.total_samples * m_codec_info.channels + : std::numeric_limits::max(); - size_t samples_written = 0; + size_t pcm_written = 0; switch (m_format) { case AjmFormatEncoding::S16: - samples_written = WriteOutputSamples(output, skipped_samples, max_samples); + pcm_written = WriteOutputSamples(output, skip_samples, max_pcm); break; case AjmFormatEncoding::S32: - samples_written = WriteOutputSamples(output, skipped_samples, max_samples); + pcm_written = WriteOutputSamples(output, skip_samples, max_pcm); break; case AjmFormatEncoding::Float: - samples_written = WriteOutputSamples(output, skipped_samples, max_samples); + pcm_written = WriteOutputSamples(output, skip_samples, max_pcm); break; default: 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; if ((m_num_frames % m_codec_info.framesInSuperframe) == 0) { if (m_superframe_bytes_remain) { @@ -126,18 +118,28 @@ std::tuple AjmAt9Decoder::ProcessData(std::span& in_buf, SparseOut 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{ .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), + .bitrate = u32((m_codec_info.samplingRate * m_codec_info.superframeSize * 8) / + (m_codec_info.framesInSuperframe * m_codec_info.frameSamples)), .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 diff --git a/src/core/libraries/ajm/ajm_at9.h b/src/core/libraries/ajm/ajm_at9.h index e5f65db9..689681de 100644 --- a/src/core/libraries/ajm/ajm_at9.h +++ b/src/core/libraries/ajm/ajm_at9.h @@ -33,15 +33,13 @@ struct AjmAt9Decoder final : AjmCodec { void Reset() override; void Initialize(const void* buffer, u32 buffer_size) override; - void GetInfo(void* out_info) override; - AjmSidebandFormat GetFormat() override; + void GetInfo(void* out_info) const override; + AjmSidebandFormat GetFormat() const override; + u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override; std::tuple ProcessData(std::span& input, SparseOutputBuffer& output, - AjmSidebandGaplessDecode& gapless, - std::optional max_samples) override; + AjmInstanceGapless& gapless) override; private: - u8 GetPointCodeSize(); - template size_t WriteOutputSamples(SparseOutputBuffer& output, u32 skipped_samples, u32 max_samples) { std::span pcm_data{reinterpret_cast(m_pcm_buffer.data()), diff --git a/src/core/libraries/ajm/ajm_batch.cpp b/src/core/libraries/ajm/ajm_batch.cpp index 5c76beae..b1cec88b 100644 --- a/src/core/libraries/ajm/ajm_batch.cpp +++ b/src/core/libraries/ajm/ajm_batch.cpp @@ -135,7 +135,10 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) { case Identifier::AjmIdentInputControlBuf: { ASSERT_MSG(!input_control_buffer.has_value(), "Only one instance of input control buffer is allowed per job"); - input_control_buffer = batch_buffer.Consume(); + const auto& buffer = batch_buffer.Consume(); + if (buffer.p_address != nullptr && buffer.size != 0) { + input_control_buffer = buffer; + } break; } case Identifier::AjmIdentControlFlags: @@ -155,19 +158,27 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) { case Identifier::AjmIdentInlineBuf: { ASSERT_MSG(!output_control_buffer.has_value(), "Only one instance of inline buffer is allowed per job"); - inline_buffer = batch_buffer.Consume(); + const auto& buffer = batch_buffer.Consume(); + if (buffer.p_address != nullptr && buffer.size != 0) { + inline_buffer = buffer; + } break; } case Identifier::AjmIdentOutputRunBuf: { auto& buffer = batch_buffer.Consume(); u8* p_begin = reinterpret_cast(buffer.p_address); - job.output.buffers.emplace_back(std::span(p_begin, p_begin + buffer.size)); + if (p_begin != nullptr && buffer.size != 0) { + job.output.buffers.emplace_back(std::span(p_begin, p_begin + buffer.size)); + } break; } case Identifier::AjmIdentOutputControlBuf: { ASSERT_MSG(!output_control_buffer.has_value(), "Only one instance of output control buffer is allowed per job"); - output_control_buffer = batch_buffer.Consume(); + const auto& buffer = batch_buffer.Consume(); + if (buffer.p_address != nullptr && buffer.size != 0) { + output_control_buffer = buffer; + } break; } default: diff --git a/src/core/libraries/ajm/ajm_instance.cpp b/src/core/libraries/ajm/ajm_instance.cpp index 85a6f54a..4e04eea7 100644 --- a/src/core/libraries/ajm/ajm_instance.cpp +++ b/src/core/libraries/ajm/ajm_instance.cpp @@ -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_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) { switch (codec_type) { case AjmCodecType::At9Dec: { @@ -30,7 +43,8 @@ AjmInstance::AjmInstance(AjmCodecType codec_type, AjmInstanceFlags flags) : m_fl break; } case AjmCodecType::Mp3Dec: { - m_codec = std::make_unique(AjmFormatEncoding(flags.format)); + m_codec = std::make_unique(AjmFormatEncoding(flags.format), + AjmMp3CodecFlags(flags.codec)); break; } default: @@ -45,7 +59,6 @@ void AjmInstance::ExecuteJob(AjmJob& job) { m_format = {}; m_gapless = {}; m_resample_parameters = {}; - m_gapless_samples = 0; m_total_samples = 0; m_codec->Reset(); } @@ -64,27 +77,47 @@ void AjmInstance::ExecuteJob(AjmJob& job) { } 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 (params.total_samples != 0) { + 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()) { - u32 frames_decoded = 0; std::span in_buf(job.input.buffer); SparseOutputBuffer out_buf(job.output.buffers); + u32 frames_decoded = 0; auto in_size = in_buf.size(); auto out_size = out_buf.Size(); - while (!in_buf.empty() && !out_buf.IsEmpty() && !IsGaplessEnd()) { - const auto samples_remain = - m_gapless.total_samples != 0 - ? std::optional{m_gapless.total_samples - m_gapless_samples} - : std::optional{}; - const auto [nframes, nsamples] = - m_codec->ProcessData(in_buf, out_buf, m_gapless, samples_remain); + while (!in_buf.empty() && !out_buf.IsEmpty() && !m_gapless.IsEnd()) { + if (!HasEnoughSpace(out_buf)) { + if (job.output.p_mframe == nullptr || frames_decoded == 0) { + job.output.p_result->result = ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM; + break; + } + } + + const auto [nframes, nsamples] = m_codec->ProcessData(in_buf, out_buf, m_gapless); frames_decoded += nframes; 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) { 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) { *job.output.p_format = m_codec->GetFormat(); } 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) { m_codec->GetInfo(job.output.p_codec_info); } } -bool AjmInstance::IsGaplessEnd() { - return m_gapless.total_samples != 0 && m_gapless_samples >= m_gapless.total_samples; +bool AjmInstance::HasEnoughSpace(const SparseOutputBuffer& output) const { + return output.Size() >= m_codec->GetNextFrameSize(m_gapless); } } // namespace Libraries::Ajm diff --git a/src/core/libraries/ajm/ajm_instance.h b/src/core/libraries/ajm/ajm_instance.h index d1d398ae..9d0f6b9f 100644 --- a/src/core/libraries/ajm/ajm_instance.h +++ b/src/core/libraries/ajm/ajm_instance.h @@ -14,6 +14,8 @@ namespace Libraries::Ajm { +u8 GetPCMSize(AjmFormatEncoding format); + class SparseOutputBuffer { public: SparseOutputBuffer(std::span> chunks) @@ -33,14 +35,17 @@ public: ++m_current; } } + if (!pcm.empty()) { + LOG_ERROR(Lib_Ajm, "Could not write {} samples", pcm.size()); + } return samples_written; } - bool IsEmpty() { + bool IsEmpty() const { return m_current == m_chunks.end(); } - size_t Size() { + size_t Size() const { size_t result = 0; for (auto it = m_current; it != m_chunks.end(); ++it) { result += it->size(); @@ -53,17 +58,26 @@ private: std::span>::iterator m_current; }; +struct AjmInstanceGapless { + AjmSidebandGaplessDecode init{}; + AjmSidebandGaplessDecode current{}; + + bool IsEnd() const { + return init.total_samples != 0 && current.total_samples == 0; + } +}; + class AjmCodec { public: virtual ~AjmCodec() = default; 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 void GetInfo(void* out_info) const = 0; + virtual AjmSidebandFormat GetFormat() const = 0; + virtual u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const = 0; virtual std::tuple ProcessData(std::span& input, SparseOutputBuffer& output, - AjmSidebandGaplessDecode& gapless, - std::optional max_samples_per_channel) = 0; + AjmInstanceGapless& gapless) = 0; }; class AjmInstance { @@ -73,16 +87,14 @@ public: void ExecuteJob(AjmJob& job); private: - bool IsGaplessEnd(); + bool HasEnoughSpace(const SparseOutputBuffer& output) const; + std::optional GetNumRemainingSamples() const; AjmInstanceFlags m_flags{}; AjmSidebandFormat m_format{}; - AjmSidebandGaplessDecode m_gapless{}; + AjmInstanceGapless m_gapless{}; AjmSidebandResampleParameters m_resample_parameters{}; - - u32 m_gapless_samples{}; u32 m_total_samples{}; - std::unique_ptr m_codec; }; diff --git a/src/core/libraries/ajm/ajm_mp3.cpp b/src/core/libraries/ajm/ajm_mp3.cpp index 90196bb9..3b464238 100644 --- a/src/core/libraries/ajm/ajm_mp3.cpp +++ b/src/core/libraries/ajm/ajm_mp3.cpp @@ -15,18 +15,50 @@ extern "C" { namespace Libraries::Ajm { // Following tables have been reversed from AJM library -static constexpr std::array, 3> SamplerateTable = {{ - {0x5622, 0x5DC0, 0x3E80}, - {0xAC44, 0xBB80, 0x7D00}, - {0x2B11, 0x2EE0, 0x1F40}, -}}; +static constexpr std::array, 4> Mp3SampleRateTable = { + std::array{11025, 12000, 8000, 0}, + std::array{0, 0, 0, 0}, + std::array{22050, 24000, 16000, 0}, + std::array{44100, 48000, 32000, 0}, +}; -static constexpr std::array, 2> BitrateTable = {{ - {0, 0x20, 0x28, 0x30, 0x38, 0x40, 0x50, 0x60, 0x70, 0x80, 0xA0, 0xC0, 0xE0, 0x100, 0x140}, - {0, 0x8, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0}, -}}; +static constexpr std::array, 4> Mp3BitRateTable = { + std::array{0, 8, 16, 24, 32, 40, 48, 56, 64, 0, 0, 0, 0, 0, 0, 0}, + std::array{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + std::array{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0}, + std::array{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0}, +}; -static constexpr std::array 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) { switch (format) { @@ -62,7 +94,7 @@ AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) { swr_init(m_swr_context); const auto res = swr_convert_frame(m_swr_context, new_frame, frame); 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(&frame); return nullptr; @@ -71,48 +103,57 @@ AVFrame* AjmMp3Decoder::ConvertAudioFrame(AVFrame* frame) { return new_frame; } -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(); -} - -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"); +AjmMp3Decoder::AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags) + : m_format(format), m_flags(flags), m_codec(avcodec_find_decoder(AV_CODEC_ID_MP3)), + m_codec_context(avcodec_alloc_context3(m_codec)), m_parser(av_parser_init(m_codec->id)) { int ret = avcodec_open2(m_codec_context, m_codec, nullptr); 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(out_info); + if (m_header.has_value()) { + auto* header = reinterpret_cast(&m_header.value()); + info->header = std::byteswap(m_header.value()); + info->has_crc = header->protection_type; + info->channel_mode = static_cast(header->channel_mode); + info->mode_extension = header->mode_ext_idx; + info->copyright = header->copyright; + info->original = header->original; + info->emphasis = header->emphasis; + } } std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOutputBuffer& output, - AjmSidebandGaplessDecode& gapless, - std::optional max_samples_per_channel) { + AjmInstanceGapless& gapless) { AVPacket* pkt = av_packet_alloc(); + if ((!m_header.has_value() || m_frame_samples == 0) && in_buf.size() >= 4) { + m_header = std::byteswap(*reinterpret_cast(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(), in_buf.size(), AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0); ASSERT_MSG(ret >= 0, "Error while parsing {}", ret); in_buf = in_buf.subspan(ret); u32 frames_decoded = 0; - u32 samples_decoded = 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::max(); + u32 samples_written = 0; if (pkt->size) { // Send the packet with the compressed data to the decoder @@ -135,31 +176,38 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOut frame = ConvertAudioFrame(frame); frames_decoded += 1; - u32 skipped_samples = 0; - if (gapless.skipped_samples < gapless.skip_samples) { - skipped_samples = std::min(u32(frame->nb_samples), - u32(gapless.skip_samples - gapless.skipped_samples)); - gapless.skipped_samples += skipped_samples; + u32 skip_samples = 0; + if (gapless.current.skip_samples > 0) { + skip_samples = std::min(u16(frame->nb_samples), gapless.current.skip_samples); + gapless.current.skip_samples -= skip_samples; } + const auto max_pcm = + gapless.init.total_samples != 0 + ? gapless.current.total_samples * m_codec_context->ch_layout.nb_channels + : std::numeric_limits::max(); + + u32 pcm_written = 0; switch (m_format) { case AjmFormatEncoding::S16: - samples_decoded += - WriteOutputSamples(frame, output, skipped_samples, max_samples); + pcm_written = WriteOutputPCM(frame, output, skip_samples, max_pcm); break; case AjmFormatEncoding::S32: - samples_decoded += - WriteOutputSamples(frame, output, skipped_samples, max_samples); + pcm_written = WriteOutputPCM(frame, output, skip_samples, max_pcm); break; case AjmFormatEncoding::Float: - samples_decoded += - WriteOutputSamples(frame, output, skipped_samples, max_samples); + pcm_written = WriteOutputPCM(frame, output, skip_samples, max_pcm); break; default: 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); } @@ -167,38 +215,221 @@ std::tuple AjmMp3Decoder::ProcessData(std::span& in_buf, SparseOut 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 + 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) { 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) { + + if (p_begin == nullptr || stream_size < 4 || frame == nullptr) { return ORBIS_AJM_ERROR_INVALID_PARAMETER; } - const u32 unk_idx = buf[1] >> 3 & 1; - const s32 version_idx = (buf[1] >> 3 & 3) ^ 2; - const s32 sr_idx = buf[2] >> 2 & 3; - const s32 br_idx = (buf[2] >> 4) & 0xf; - const s32 padding_bit = (buf[2] >> 1) & 0x1; + const auto* p_current = p_begin; + + auto bytes = std::byteswap(*reinterpret_cast(p_current)); + p_current += 4; + auto header = reinterpret_cast(&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->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(16); + } + + if (header->version == Mp3AudioVersion::V1) { + // main_data_begin = reader.Read(9); + // if (header->channel_mode == Mp3ChannelMode::SingleChannel) { + // private_bits = reader.Read(5); + // } else { + // private_bits = reader.Read(3); + // } + // for (u32 ch = 0; ch < frame->num_channels; ++ch) { + // for (u8 band = 0; band < 4; ++band) { + // scfsi[ch][band] = reader.Read(1); + // } + // } + if (header->channel_mode == Mp3ChannelMode::SingleChannel) { + reader.Skip(18); + } else { + reader.Skip(20); + } + } else { + // main_data_begin = reader.Read(8); + // if (header->channel_mode == Mp3ChannelMode::SingleChannel) { + // private_bits = reader.Read(1); + // } else { + // private_bits = reader.Read(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(12); + part2_3_length += reader.Read(12); + // big_values[gr][ch] = reader.Read(9); + // global_main[gr][ch] = reader.Read(8); + // if (header->version == Mp3AudioVersion::V1) { + // scalefac_compress[gr][ch] = reader.Read(4); + // } else { + // scalefac_compress[gr][ch] = reader.Read(9); + // } + // window_switching_flag = reader.Read(1); + // if (window_switching_flag) { + // block_type[gr][ch] = reader.Read(2); + // mixed_block_flag[gr][ch] = reader.Read(1); + // for (u8 region = 0; region < 2; ++region) { + // table_select[gr][ch][region] = reader.Read(5); + // } + // for (u8 window = 0; window < 3; ++window) { + // subblock_gain[gr][ch][window] = reader.Read(3); + // } + // } else { + // for (u8 region = 0; region < 3; ++region) { + // table_select[gr][ch][region] = reader.Read(5); + // } + // region0_count[gr][ch] = reader.Read(4); + // region1_count[gr][ch] = reader.Read(3); + // } + // if (header->version == Mp3AudioVersion::V1) { + // preflag[gr][ch] = reader.Read(1); + // } + // scalefac_scale[gr][ch] = reader.Read(1); + // count1table_select[gr][ch] = reader.Read(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(p_fgh + 1)); + frame->total_samples = std::byteswap(*reinterpret_cast(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; } -AjmSidebandFormat AjmMp3Decoder::GetFormat() { - LOG_ERROR(Lib_Ajm, "Unimplemented"); - return AjmSidebandFormat{}; +AjmSidebandFormat AjmMp3Decoder::GetFormat() const { + 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 diff --git a/src/core/libraries/ajm/ajm_mp3.h b/src/core/libraries/ajm/ajm_mp3.h index b5da19e7..70c94955 100644 --- a/src/core/libraries/ajm/ajm_mp3.h +++ b/src/core/libraries/ajm/ajm_mp3.h @@ -13,7 +13,19 @@ struct SwrContext; 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 static constexpr u8 SYNCWORDH = 0xff; @@ -51,39 +63,40 @@ struct AjmSidebandDecMp3CodecInfo { class AjmMp3Decoder : public AjmCodec { public: - explicit AjmMp3Decoder(AjmFormatEncoding format); + explicit AjmMp3Decoder(AjmFormatEncoding format, AjmMp3CodecFlags flags); ~AjmMp3Decoder() override; void Reset() override; void Initialize(const void* buffer, u32 buffer_size) override {} - void GetInfo(void* out_info) override; - AjmSidebandFormat GetFormat() override; + void GetInfo(void* out_info) const override; + AjmSidebandFormat GetFormat() const override; + u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override; std::tuple ProcessData(std::span& input, SparseOutputBuffer& output, - AjmSidebandGaplessDecode& gapless, - std::optional max_samples_per_channel) override; + AjmInstanceGapless& gapless) override; static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl, AjmDecMp3ParseFrame* frame); private: template - 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 pcm_data(reinterpret_cast(frame->data[0]), size >> 1); + size_t WriteOutputPCM(AVFrame* frame, SparseOutputBuffer& output, u32 skipped_samples, + u32 max_pcm) { + std::span pcm_data(reinterpret_cast(frame->data[0]), + frame->nb_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); - const auto samples_written = output.Write(pcm_data.subspan(0, pcm_size)); - return samples_written / frame->ch_layout.nb_channels; + return output.Write(pcm_data.subspan(0, std::min(u32(pcm_data.size()), max_pcm))); } AVFrame* ConvertAudioFrame(AVFrame* frame); const AjmFormatEncoding m_format; + const AjmMp3CodecFlags m_flags; const AVCodec* m_codec = nullptr; AVCodecContext* m_codec_context = nullptr; AVCodecParserContext* m_parser = nullptr; SwrContext* m_swr_context = nullptr; + std::optional m_header; + u32 m_frame_samples = 0; }; } // namespace Libraries::Ajm