diff options
| author | jacqueline <me@jacqueline.id.au> | 2023-07-07 18:26:10 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2023-07-07 18:26:10 +1000 |
| commit | 8f8bc1f088b389a683735d626cbce9adb1f6dc17 (patch) | |
| tree | 215b669021d778b4b64a38cab662b041142d5957 /src/codecs/mad.cpp | |
| parent | 739f495fe26e7bd291e545f50b27c185991dcfbc (diff) | |
| download | tangara-fw-8f8bc1f088b389a683735d626cbce9adb1f6dc17.tar.gz | |
vbr-compatible mp3 duration :)
Diffstat (limited to 'src/codecs/mad.cpp')
| -rw-r--r-- | src/codecs/mad.cpp | 68 |
1 files changed, 65 insertions, 3 deletions
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 <sys/_stdint.h> #include <cstdint> +#include <cstring> #include <optional> #include "mad.h" @@ -88,9 +89,12 @@ auto MadMp3Decoder::BeginStream(const cpp::span<const std::byte> 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<const std::byte> input, } } +/* + * Implementation taken from SDL_mixer and modified. Original is zlib-licensed, + * copyright (C) 1997-2022 Sam Lantinga <slouken@libsdl.org> + */ +auto MadMp3Decoder::GetVbrLength(const mad_header& header) + -> std::optional<uint32_t> { + 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 |
