Fix (i think?) mysterious overly large reads in libmad

custom
jacqueline 2 years ago
parent b58b072d2d
commit cde8002df4
  1. 10
      src/audio/audio_task.cpp
  2. 20
      src/audio/fatfs_audio_input.cpp
  3. 2
      src/codecs/include/mad.hpp
  4. 40
      src/codecs/mad.cpp

@ -173,11 +173,11 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) {
float samples_sunk = bytes_sunk; float samples_sunk = bytes_sunk;
samples_sunk /= pcm.channels; samples_sunk /= pcm.channels;
int8_t bps = pcm.bits_per_sample; // Samples must be aligned to 16 bits. The number of actual bytes per
if (bps == 24) { // sample is therefore the bps divided by 16, rounded up (align to word),
bps = 32; // times two (convert to bytes).
} uint8_t bytes_per_sample = ((pcm.bits_per_sample + 16 - 1) / 16) * 2;
samples_sunk /= (bps / 8); samples_sunk /= bytes_per_sample;
current_sample_in_second += samples_sunk; current_sample_in_second += samples_sunk;
while (current_sample_in_second >= pcm.sample_rate) { while (current_sample_in_second >= pcm.sample_rate) {

@ -9,6 +9,7 @@
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <future> #include <future>
#include <memory> #include <memory>
@ -71,8 +72,7 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool {
database::TrackTags tags; database::TrackTags tags;
if (!tag_parser.ReadAndParseTags(path, &tags)) { if (!tag_parser.ReadAndParseTags(path, &tags)) {
ESP_LOGE(kTag, "failed to read tags"); ESP_LOGE(kTag, "failed to read tags");
tags.encoding = database::Encoding::kFlac; return false;
// return false;
} }
auto stream_type = ContainerToStreamType(tags.encoding); auto stream_type = ContainerToStreamType(tags.encoding);
@ -115,6 +115,8 @@ auto FatfsAudioInput::NeedsToProcess() const -> bool {
auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs, auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
OutputStream* output) -> void { OutputStream* output) -> void {
// If the next path is being given to us asynchronously, then we need to check
// in regularly to see if it's available yet.
if (pending_path_) { if (pending_path_) {
if (!pending_path_->valid()) { if (!pending_path_->valid()) {
pending_path_ = {}; pending_path_ = {};
@ -133,11 +135,15 @@ auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
return; return;
} }
// If the output buffer isn't ready for a new stream, then we need to wait.
if (!has_prepared_output_ && !output->prepare(*current_format_)) { if (!has_prepared_output_ && !output->prepare(*current_format_)) {
return; return;
} }
has_prepared_output_ = true; has_prepared_output_ = true;
// Performing many small reads is inefficient; it's better to do fewer, larger
// reads. Try to achieve this by only reading in new bytes if the output
// buffer has been mostly drained.
std::size_t max_size = output->data().size_bytes(); std::size_t max_size = output->data().size_bytes();
if (max_size < output->data().size_bytes() / 2) { if (max_size < output->data().size_bytes() / 2) {
return; return;
@ -148,6 +154,7 @@ auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
f_read(&current_file_, output->data().data(), max_size, &size); f_read(&current_file_, output->data().data(), max_size, &size);
if (result != FR_OK) { if (result != FR_OK) {
ESP_LOGE(kTag, "file I/O error %d", result); ESP_LOGE(kTag, "file I/O error %d", result);
output->mark_producer_finished();
// TODO(jacqueline): Handle errors. // TODO(jacqueline): Handle errors.
return; return;
} }
@ -155,6 +162,15 @@ auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
output->add(size); output->add(size);
if (size < max_size || f_eof(&current_file_)) { if (size < max_size || f_eof(&current_file_)) {
// HACK: In order to decode the last frame of a file, libmad requires 8
// 0-bytes ( == MAD_GUARD_BYTES) to be appended to the end of the stream.
// It would be better to do this within mad.cpp, but so far it's the only
// decoder that has such a requirement.
if (current_container_ == database::Encoding::kMp3) {
std::fill_n(output->data().begin(), 8, std::byte(0));
output->add(8);
}
f_close(&current_file_); f_close(&current_file_);
is_file_open_ = false; is_file_open_ = false;
has_prepared_output_ = false; has_prepared_output_ = false;

@ -48,7 +48,7 @@ class MadMp3Decoder : public ICodec {
int current_sample_; int current_sample_;
auto GetInputPosition() -> std::size_t; auto GetBytesUsed(std::size_t) -> std::size_t;
}; };
} // namespace codecs } // namespace codecs

@ -13,6 +13,7 @@
#include "mad.h" #include "mad.h"
#include "codec.hpp" #include "codec.hpp"
#include "esp_log.h"
#include "result.hpp" #include "result.hpp"
#include "types.hpp" #include "types.hpp"
@ -43,9 +44,13 @@ MadMp3Decoder::~MadMp3Decoder() {
mad_synth_finish(&synth_); mad_synth_finish(&synth_);
} }
auto MadMp3Decoder::GetInputPosition() -> std::size_t { auto MadMp3Decoder::GetBytesUsed(std::size_t buffer_size) -> std::size_t {
assert(stream_.next_frame >= stream_.buffer); if (stream_.next_frame) {
return stream_.next_frame - stream_.buffer; std::size_t remaining = stream_.bufend - stream_.next_frame;
return buffer_size - remaining;
} else {
return stream_.bufend - stream_.buffer;
}
} }
auto MadMp3Decoder::BeginStream(const cpp::span<const std::byte> input) auto MadMp3Decoder::BeginStream(const cpp::span<const std::byte> input)
@ -68,13 +73,13 @@ auto MadMp3Decoder::BeginStream(const cpp::span<const std::byte> input)
continue; continue;
} }
if (stream_.error == MAD_ERROR_BUFLEN) { if (stream_.error == MAD_ERROR_BUFLEN) {
return {GetInputPosition(), cpp::fail(Error::kOutOfInput)}; return {GetBytesUsed(input.size_bytes()), cpp::fail(Error::kOutOfInput)};
} }
return {GetInputPosition(), cpp::fail(Error::kMalformedData)}; return {GetBytesUsed(input.size_bytes()), cpp::fail(Error::kMalformedData)};
} }
uint8_t channels = MAD_NCHANNELS(&header); uint8_t channels = MAD_NCHANNELS(&header);
return {GetInputPosition(), return {GetBytesUsed(input.size_bytes()),
OutputFormat{ OutputFormat{
.num_channels = channels, .num_channels = channels,
.bits_per_sample = 24, // We always scale to 24 bits .bits_per_sample = 24, // We always scale to 24 bits
@ -85,6 +90,7 @@ auto MadMp3Decoder::BeginStream(const cpp::span<const std::byte> input)
auto MadMp3Decoder::ContinueStream(cpp::span<const std::byte> input, auto MadMp3Decoder::ContinueStream(cpp::span<const std::byte> input,
cpp::span<std::byte> output) cpp::span<std::byte> output)
-> Result<OutputInfo> { -> Result<OutputInfo> {
std::size_t bytes_read = 0;
if (current_sample_ < 0) { if (current_sample_ < 0) {
mad_stream_buffer(&stream_, mad_stream_buffer(&stream_,
reinterpret_cast<const unsigned char*>(input.data()), reinterpret_cast<const unsigned char*>(input.data()),
@ -101,15 +107,18 @@ auto MadMp3Decoder::ContinueStream(cpp::span<const std::byte> input,
if (stream_.error == MAD_ERROR_BUFLEN) { if (stream_.error == MAD_ERROR_BUFLEN) {
// The decoder ran out of bytes before it completed a frame. We // The decoder ran out of bytes before it completed a frame. We
// need to return back to the caller to give us more data. // need to return back to the caller to give us more data.
return {GetInputPosition(), cpp::fail(Error::kOutOfInput)}; return {GetBytesUsed(input.size_bytes()),
cpp::fail(Error::kOutOfInput)};
} }
// The error is unrecoverable. Give up. // The error is unrecoverable. Give up.
return {GetInputPosition(), cpp::fail(Error::kMalformedData)}; return {GetBytesUsed(input.size_bytes()),
cpp::fail(Error::kMalformedData)};
} }
// We've successfully decoded a frame! Now synthesize samples to write out. // We've successfully decoded a frame! Now synthesize samples to write out.
mad_synth_frame(&synth_, &frame_); mad_synth_frame(&synth_, &frame_);
current_sample_ = 0; current_sample_ = 0;
bytes_read = GetBytesUsed(input.size_bytes());
} }
size_t output_byte = 0; size_t output_byte = 0;
@ -117,7 +126,7 @@ auto MadMp3Decoder::ContinueStream(cpp::span<const std::byte> input,
if (output_byte + (4 * synth_.pcm.channels) >= output.size()) { if (output_byte + (4 * synth_.pcm.channels) >= output.size()) {
// We can't fit the next sample into the buffer. Stop now, and also avoid // We can't fit the next sample into the buffer. Stop now, and also avoid
// writing the sample for only half the channels. // writing the sample for only half the channels.
return {GetInputPosition(), OutputInfo{.bytes_written = output_byte, return {bytes_read, OutputInfo{.bytes_written = output_byte,
.is_finished_writing = false}}; .is_finished_writing = false}};
} }
@ -135,7 +144,7 @@ auto MadMp3Decoder::ContinueStream(cpp::span<const std::byte> input,
// We wrote everything! Reset, ready for the next frame. // We wrote everything! Reset, ready for the next frame.
current_sample_ = -1; current_sample_ = -1;
return {GetInputPosition(), OutputInfo{.bytes_written = output_byte, return {bytes_read, OutputInfo{.bytes_written = output_byte,
.is_finished_writing = true}}; .is_finished_writing = true}};
} }
@ -160,7 +169,8 @@ auto MadMp3Decoder::SeekStream(cpp::span<const std::byte> input,
} else { } else {
// Don't bother checking for other errors; if the first part of the // Don't bother checking for other errors; if the first part of the
// stream doesn't even contain a header then something's gone wrong. // stream doesn't even contain a header then something's gone wrong.
return {GetInputPosition(), cpp::fail(Error::kMalformedData)}; return {GetBytesUsed(input.size_bytes()),
cpp::fail(Error::kMalformedData)};
} }
} }
@ -184,10 +194,12 @@ auto MadMp3Decoder::SeekStream(cpp::span<const std::byte> input,
continue; continue;
} }
if (stream_.error == MAD_ERROR_BUFLEN) { if (stream_.error == MAD_ERROR_BUFLEN) {
return {GetInputPosition(), cpp::fail(Error::kOutOfInput)}; return {GetBytesUsed(input.size_bytes()),
cpp::fail(Error::kOutOfInput)};
} }
// The error is unrecoverable. Give up. // The error is unrecoverable. Give up.
return {GetInputPosition(), cpp::fail(Error::kMalformedData)}; return {GetBytesUsed(input.size_bytes()),
cpp::fail(Error::kMalformedData)};
} }
if (frames_to_go <= 1) { if (frames_to_go <= 1) {
@ -202,7 +214,7 @@ auto MadMp3Decoder::SeekStream(cpp::span<const std::byte> input,
// call. // call.
current_sample_ = current_sample_ =
(target_sample > current_sample) ? target_sample - current_sample : 0; (target_sample > current_sample) ? target_sample - current_sample : 0;
return {GetInputPosition(), {}}; return {GetBytesUsed(input.size_bytes()), {}};
} }
} }
} }

Loading…
Cancel
Save