diff --git a/src/codecs/include/mad.hpp b/src/codecs/include/mad.hpp index fba3ef11..60a0b81c 100644 --- a/src/codecs/include/mad.hpp +++ b/src/codecs/include/mad.hpp @@ -56,7 +56,14 @@ class MadMp3Decoder : public ICodec { std::unique_ptr frame_; std::unique_ptr synth_; - int current_sample_; + // Count of samples processed in the current frame (channels combined) + int current_frame_sample_; + // Count of samples processed in the current stream (channels separate, i.e. usually x2) + int current_stream_sample_; + // How many samples in the current stream (channels separate) with encoder delay/padding removed + int total_samples_; + // Encoder delay, i.e. how many samples to skip at the start of the stream + int skip_samples_; bool is_eof_; bool is_eos_; }; diff --git a/src/codecs/mad.cpp b/src/codecs/mad.cpp index af74b244..538f0715 100644 --- a/src/codecs/mad.cpp +++ b/src/codecs/mad.cpp @@ -37,7 +37,10 @@ MadMp3Decoder::MadMp3Decoder() synth_(reinterpret_cast( heap_caps_malloc(sizeof(mad_synth), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT))), - current_sample_(-1), + current_frame_sample_(-1), + current_stream_sample_(0), + total_samples_(0), + skip_samples_(0), is_eof_(false), is_eos_(false) { mad_stream_init(stream_.get()); @@ -63,6 +66,8 @@ auto MadMp3Decoder::OpenStream(std::shared_ptr input, uint32_t offset) -> cpp::result { input_ = input; + current_stream_sample_ = 0; + auto id3size = SkipID3Tags(*input); // To get the output format for MP3 streams, we simply need to decode the @@ -115,6 +120,7 @@ auto MadMp3Decoder::OpenStream(std::shared_ptr input, uint32_t offset) cbr_length = (input->Size().value() * 8) / header.bitrate; output.total_samples = cbr_length * output.sample_rate_hz * channels; } + total_samples_ = output.total_samples.value(); // header.bitrate is only for CBR, but we've calculated total samples for VBR // and CBR, so we can use that to calculate sample size and therefore bitrate. @@ -124,6 +130,11 @@ auto MadMp3Decoder::OpenStream(std::shared_ptr input, uint32_t offset) output.bitrate_kbps = static_cast(output.sample_rate_hz * channels * sample_size / 1024); } + // For gapless MP3s, save samples to skip + if (mp3_info) { + skip_samples_ = mp3_info->starting_sample; + } + if (offset > 1 && cbr_length > 0) { // Constant bitrate seeking uint64_t skip_bytes = header.bitrate * (offset - 1) / 8; @@ -199,7 +210,7 @@ auto MadMp3Decoder::OpenStream(std::shared_ptr input, uint32_t offset) auto MadMp3Decoder::DecodeTo(std::span output) -> cpp::result { - if (current_sample_ < 0 && !is_eos_) { + if (current_frame_sample_ < 0 && !is_eos_) { if (!is_eof_) { is_eof_ = buffer_.Refill(input_.get()); if (is_eof_) { @@ -243,14 +254,21 @@ auto MadMp3Decoder::DecodeTo(std::span output) // We've successfully decoded a frame! Now synthesize samples to write // out. mad_synth_frame(synth_.get(), frame_.get()); - current_sample_ = 0; + current_frame_sample_ = 0; return GetBytesUsed(); }); } size_t output_sample = 0; - if (current_sample_ >= 0) { - while (current_sample_ < synth_->pcm.length) { + if (current_frame_sample_ >= 0) { + // Skip any gap samples indicated by the headers + while (skip_samples_ > 0) { + skip_samples_--; + current_frame_sample_++; + } + + // Process samples until we hit the end of the frame or stream + while (current_frame_sample_ < synth_->pcm.length && current_stream_sample_ <= total_samples_) { if (output_sample + synth_->pcm.channels >= output.size()) { // We can't fit the next full frame into the buffer. return OutputInfo{.samples_written = output_sample, @@ -259,14 +277,18 @@ auto MadMp3Decoder::DecodeTo(std::span output) for (int channel = 0; channel < synth_->pcm.channels; channel++) { output[output_sample++] = - sample::FromMad(synth_->pcm.samples[channel][current_sample_]); + sample::FromMad(synth_->pcm.samples[channel][current_frame_sample_]); } - current_sample_++; + current_frame_sample_++; + current_stream_sample_ += synth_->pcm.channels; + } + if (current_stream_sample_ > total_samples_) { + is_eos_ = true; } } // We wrote everything! Reset, ready for the next frame. - current_sample_ = -1; + current_frame_sample_ = -1; return OutputInfo{.samples_written = output_sample, .is_stream_finished = is_eos_}; }