From 8f8bc1f088b389a683735d626cbce9adb1f6dc17 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Fri, 7 Jul 2023 18:26:10 +1000 Subject: [PATCH] vbr-compatible mp3 duration :) --- src/audio/audio_fsm.cpp | 4 +-- src/codecs/include/mad.hpp | 2 ++ src/codecs/mad.cpp | 68 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp index ef33d583..5f4f8783 100644 --- a/src/audio/audio_fsm.cpp +++ b/src/audio/audio_fsm.cpp @@ -144,8 +144,8 @@ void Playback::react(const QueueUpdate& ev) { } void Playback::react(const PlaybackUpdate& ev) { - // ESP_LOGI(kTag, "elapsed: %lu, total: %lu", ev.seconds_elapsed, - // ev.seconds_total); + ESP_LOGI(kTag, "elapsed: %lu, total: %lu", ev.seconds_elapsed, + ev.seconds_total); } void Playback::react(const internal::InputFileOpened& ev) {} diff --git a/src/codecs/include/mad.hpp b/src/codecs/include/mad.hpp index d2643230..fbae560c 100644 --- a/src/codecs/include/mad.hpp +++ b/src/codecs/include/mad.hpp @@ -42,6 +42,8 @@ class MadMp3Decoder : public ICodec { -> Result override; private: + auto GetVbrLength(const mad_header& header) -> std::optional; + mad_stream stream_; mad_frame frame_; mad_synth synth_; diff --git a/src/codecs/mad.cpp b/src/codecs/mad.cpp index 81daeb9f..ca9a0f6e 100644 --- a/src/codecs/mad.cpp +++ b/src/codecs/mad.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include "mad.h" @@ -88,9 +89,12 @@ auto MadMp3Decoder::BeginStream(const cpp::span input) .bits_per_second = {}, }; - // TODO(jacqueline): Support VBR. Although maybe libtags is the better place - // to handle this? - output.bits_per_second = header.bitrate; + auto vbr_length = GetVbrLength(header); + if (vbr_length) { + output.duration_seconds = vbr_length; + } else { + output.bits_per_second = header.bitrate; + } return {GetBytesUsed(input.size_bytes()), output}; } @@ -227,4 +231,62 @@ auto MadMp3Decoder::SeekStream(cpp::span input, } } +/* + * Implementation taken from SDL_mixer and modified. Original is zlib-licensed, + * copyright (C) 1997-2022 Sam Lantinga + */ +auto MadMp3Decoder::GetVbrLength(const mad_header& header) + -> std::optional { + if (!stream_.this_frame || !stream_.next_frame || + stream_.next_frame <= stream_.this_frame || + (stream_.next_frame - stream_.this_frame) < 48) { + return {}; + } + + int mpeg_version = (stream_.this_frame[1] >> 3) & 0x03; + + int xing_offset = 0; + switch (mpeg_version) { + case 0x03: /* MPEG1 */ + if (header.mode == MAD_MODE_SINGLE_CHANNEL) { + xing_offset = 4 + 17; + } else { + xing_offset = 4 + 32; + } + break; + default: /* MPEG2 and MPEG2.5 */ + if (header.mode == MAD_MODE_SINGLE_CHANNEL) { + xing_offset = 4 + 17; + } else { + xing_offset = 4 + 9; + } + break; + } + + uint32_t samples_per_frame = 32 * MAD_NSBSAMPLES(&header); + + unsigned char const* frames_count_raw; + uint32_t frames_count = 0; + if (std::memcmp(stream_.this_frame + xing_offset, "Xing", 4) == 0 || + std::memcmp(stream_.this_frame + xing_offset, "Info", 4) == 0) { + /* Xing header to get the count of frames for VBR */ + frames_count_raw = stream_.this_frame + xing_offset + 8; + frames_count = ((uint32_t)frames_count_raw[0] << 24) + + ((uint32_t)frames_count_raw[1] << 16) + + ((uint32_t)frames_count_raw[2] << 8) + + ((uint32_t)frames_count_raw[3]); + } else if (std::memcmp(stream_.this_frame + xing_offset, "VBRI", 4) == 0) { + /* VBRI header to get the count of frames for VBR */ + frames_count_raw = stream_.this_frame + xing_offset + 14; + frames_count = ((uint32_t)frames_count_raw[0] << 24) + + ((uint32_t)frames_count_raw[1] << 16) + + ((uint32_t)frames_count_raw[2] << 8) + + ((uint32_t)frames_count_raw[3]); + } else { + return {}; + } + + return (double)(frames_count * samples_per_frame) / header.samplerate; +} + } // namespace codecs