summaryrefslogtreecommitdiff
path: root/src/codecs/mad.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/codecs/mad.cpp')
-rw-r--r--src/codecs/mad.cpp82
1 files changed, 76 insertions, 6 deletions
diff --git a/src/codecs/mad.cpp b/src/codecs/mad.cpp
index 23b4ccf6..ca9a0f6e 100644
--- a/src/codecs/mad.cpp
+++ b/src/codecs/mad.cpp
@@ -6,8 +6,10 @@
#include "mad.hpp"
#include <stdint.h>
+#include <sys/_stdint.h>
#include <cstdint>
+#include <cstring>
#include <optional>
#include "mad.h"
@@ -79,12 +81,22 @@ auto MadMp3Decoder::BeginStream(const cpp::span<const std::byte> input)
}
uint8_t channels = MAD_NCHANNELS(&header);
- return {GetBytesUsed(input.size_bytes()),
- OutputFormat{
- .num_channels = channels,
- .bits_per_sample = 24, // We always scale to 24 bits
- .sample_rate_hz = header.samplerate,
- }};
+ OutputFormat output{
+ .num_channels = channels,
+ .bits_per_sample = 24, // We always scale to 24 bits
+ .sample_rate_hz = header.samplerate,
+ .duration_seconds = {},
+ .bits_per_second = {},
+ };
+
+ 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};
}
auto MadMp3Decoder::ContinueStream(cpp::span<const std::byte> input,
@@ -219,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