summaryrefslogtreecommitdiff
path: root/src/codecs/mad.cpp
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-07-07 18:26:10 +1000
committerjacqueline <me@jacqueline.id.au>2023-07-07 18:26:10 +1000
commit8f8bc1f088b389a683735d626cbce9adb1f6dc17 (patch)
tree215b669021d778b4b64a38cab662b041142d5957 /src/codecs/mad.cpp
parent739f495fe26e7bd291e545f50b27c185991dcfbc (diff)
downloadtangara-fw-8f8bc1f088b389a683735d626cbce9adb1f6dc17.tar.gz
vbr-compatible mp3 duration :)
Diffstat (limited to 'src/codecs/mad.cpp')
-rw-r--r--src/codecs/mad.cpp68
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