back to back flac playback is working :)

custom
jacqueline 2 years ago
parent 0c81c3e1f6
commit acccd822f0
  1. 110
      src/audio/audio_decoder.cpp
  2. 7
      src/audio/audio_task.cpp
  3. 19
      src/audio/fatfs_audio_input.cpp
  4. 1
      src/audio/include/audio_decoder.hpp
  5. 1
      src/audio/include/fatfs_audio_input.hpp
  6. 19
      src/audio/include/stream_info.hpp
  7. 29
      src/audio/stream_info.cpp
  8. 11
      src/codecs/mad.cpp
  9. 1
      src/system_fsm/running.cpp

@ -36,37 +36,27 @@ AudioDecoder::AudioDecoder()
current_codec_(),
current_input_format_(),
current_output_format_(),
has_prepared_output_(false),
has_samples_to_send_(false),
has_input_remaining_(false) {}
AudioDecoder::~AudioDecoder() {}
auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) -> bool {
has_prepared_output_ = false;
current_codec_.reset();
current_input_format_.reset();
current_output_format_.reset();
if (!std::holds_alternative<StreamInfo::Encoded>(info.format)) {
return false;
}
ESP_LOGI(kTag, "got new stream");
const auto& encoded = std::get<StreamInfo::Encoded>(info.format);
// Reuse the existing codec if we can. This will help with gapless playback,
// since we can potentially just continue to decode as we were before,
// without any setup overhead.
// TODO(jacqueline): Reconsider this. It makes a lot of things harder to smash
// streams together at this layer.
/*
if (current_codec_ != nullptr && current_input_format_) {
auto cur_encoding = std::get<StreamInfo::Encoded>(*current_input_format_);
if (cur_encoding.type == encoded.type) {
ESP_LOGI(kTag, "reusing existing decoder");
current_input_format_ = info.format;
return true;
}
}
*/
const auto& new_format = std::get<StreamInfo::Encoded>(info.format);
current_input_format_ = info.format;
ESP_LOGI(kTag, "creating new decoder");
auto result = codecs::CreateCodecForType(encoded.type);
auto result = codecs::CreateCodecForType(new_format.type);
if (result.has_value()) {
current_codec_.reset(result.value());
} else {
@ -86,18 +76,31 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
auto input = inputs.begin();
const StreamInfo& info = input->info();
// Check the input stream's format has changed (or, by extension, if this is
// the first stream).
if (!current_input_format_ || *current_input_format_ != info.format) {
has_samples_to_send_ = false;
// Is this a completely new stream?
if (!current_input_format_) {
if (!ProcessStreamInfo(info)) {
// We couldn't handle the new stream. Signal to the producer that we don't
// have anything to do.
input->mark_consumer_finished();
return;
}
ESP_LOGI(kTag, "beginning new stream");
}
// Have we determined what kind of samples this stream decodes to?
if (!current_output_format_) {
auto res = current_codec_->BeginStream(input->data());
input->consume(res.first);
if (res.second.has_error()) {
// TODO(jacqueline): Handle errors.
auto err = res.second.error();
if (err == codecs::ICodec::Error::kOutOfInput) {
// We didn't manage to clear whatever front matter is before this
// stream's header. We need to call BeginStream again with more data.
return;
}
// Somthing about the stream's header was malformed. Skip it.
ESP_LOGE(kTag, "error beginning stream");
input->mark_consumer_finished();
return;
}
@ -116,59 +119,82 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs,
}
}
while (seek_to_sample_) {
if (seek_to_sample_) {
ESP_LOGI(kTag, "seeking forwards...");
auto res = current_codec_->SeekStream(input->data(), *seek_to_sample_);
input->consume(res.first);
if (res.second.has_error()) {
auto err = res.second.error();
if (err == codecs::ICodec::Error::kOutOfInput) {
return;
} else {
// TODO(jacqueline): Handle errors.
seek_to_sample_.reset();
}
} else {
seek_to_sample_.reset();
}
seek_to_sample_.reset();
}
has_input_remaining_ = true;
while (true) {
// Make sure the output buffer is ready to receive samples in our format
// before starting to process data.
// TODO(jacqueline): Pass through seek info here?
if (!output->prepare(*current_output_format_)) {
if (!has_prepared_output_ && !output->prepare(*current_output_format_)) {
ESP_LOGI(kTag, "waiting for buffer to become free");
break;
return;
}
has_prepared_output_ = true;
// Parse frames and produce samples.
auto res = current_codec_->ContinueStream(input->data(), output->data());
input->consume(res.first);
// Handle any errors during processing.
if (res.second.has_error()) {
// The codec ran out of input during processing. This is expected to
// happen throughout the stream.
if (res.second.error() == codecs::ICodec::Error::kOutOfInput) {
ESP_LOGW(kTag, "out of input");
ESP_LOGW(kTag, "(%u bytes left)", input->data().size_bytes());
ESP_LOGI(kTag, "codec needs more data");
has_input_remaining_ = false;
// We can't be halfway through sending samples if the codec is asking
// for more input.
has_samples_to_send_ = false;
input->mark_incomplete();
if (input->is_producer_finished()) {
ESP_LOGI(kTag, "codec is all done.");
// We're out of data, and so is the producer. Nothing left to be done
// with the input stream.
input->mark_consumer_finished();
// Upstream isn't going to give us any more data. Tell downstream
// that they shouldn't expact any more samples from this stream.
output->mark_producer_finished();
break;
}
} else {
// TODO(jacqueline): Handle errors.
ESP_LOGE(kTag, "codec return fatal error");
ESP_LOGE(kTag, "codec returned fatal error");
}
// Note that a codec that returns an error is not allowed to write
// samples. So it's safe to skip the latter part of the loop.
return;
}
} else {
// Some samples were written! Ensure the downstream element knows about
// them.
codecs::ICodec::OutputInfo out_info = res.second.value();
output->add(out_info.bytes_written);
has_samples_to_send_ = !out_info.is_finished_writing;
if (has_samples_to_send_) {
// We weren't able to fit all the generated samples into the output
// buffer. Stop trying; we'll finish up during the next pass.
break;
// The codec wasn't able to finish writing all of its samples into the
// output buffer. We need to return so that we can get a new buffer.
return;
}
}
}
current_codec_.reset();
current_input_format_.reset();
}
} // namespace audio

@ -125,8 +125,13 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) {
InputStream sink_stream(&raw_sink_stream);
if (sink_stream.info().bytes_in_stream == 0) {
if (sink_stream.is_producer_finished()) {
sink_stream.mark_consumer_finished();
} else {
// The user is probably about to hear a skip :(
ESP_LOGW(kTag, "!! audio sink is underbuffered !!");
}
// No new bytes to sink, so skip sinking completely.
ESP_LOGW(kTag, "no bytes to sink");
continue;
}

@ -43,6 +43,7 @@ FatfsAudioInput::FatfsAudioInput()
pending_path_(),
current_file_(),
is_file_open_(false),
has_prepared_output_(false),
current_container_(),
current_format_() {}
@ -57,10 +58,13 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool {
if (is_file_open_) {
f_close(&current_file_);
is_file_open_ = false;
has_prepared_output_ = false;
}
if (pending_path_) {
pending_path_ = {};
}
ESP_LOGI(kTag, "opening file %s", path.c_str());
database::TagParserImpl tag_parser;
@ -112,13 +116,11 @@ auto FatfsAudioInput::NeedsToProcess() const -> bool {
auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
OutputStream* output) -> void {
if (pending_path_) {
ESP_LOGI(kTag, "waiting for path");
if (!pending_path_->valid()) {
pending_path_ = {};
} else {
if (pending_path_->wait_for(std::chrono::seconds(0)) ==
std::future_status::ready) {
ESP_LOGI(kTag, "path ready!");
auto result = pending_path_->get();
if (result) {
OpenFile(*result);
@ -131,9 +133,11 @@ auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
return;
}
if (!output->prepare(*current_format_)) {
if (!has_prepared_output_ && !output->prepare(*current_format_)) {
ESP_LOGI(kTag, "waiting for buffer to free up");
return;
}
has_prepared_output_ = true;
std::size_t max_size = output->data().size_bytes();
if (max_size < output->data().size_bytes() / 2) {
@ -152,14 +156,11 @@ auto FatfsAudioInput::Process(const std::vector<InputStream>& inputs,
output->add(size);
if (size < max_size || f_eof(&current_file_)) {
ESP_LOGI(kTag, "file finished. closing.");
f_close(&current_file_);
is_file_open_ = false;
// HACK: libmad requires an 8 byte padding at the end of each file.
if (current_container_ == database::Encoding::kMp3) {
std::fill_n(output->data().begin(), 8, std::byte(0));
output->add(8);
}
has_prepared_output_ = false;
output->mark_producer_finished();
events::Dispatch<InputFileFinished, AudioState>({});
}

@ -43,6 +43,7 @@ class AudioDecoder : public IAudioElement {
std::optional<StreamInfo::Format> current_input_format_;
std::optional<StreamInfo::Format> current_output_format_;
std::optional<std::size_t> seek_to_sample_;
bool has_prepared_output_;
bool has_samples_to_send_;
bool has_input_remaining_;

@ -52,6 +52,7 @@ class FatfsAudioInput : public IAudioElement {
std::optional<std::future<std::optional<std::string>>> pending_path_;
FIL current_file_;
bool is_file_open_;
bool has_prepared_output_;
std::optional<database::Encoding> current_container_;
std::optional<StreamInfo::Format> current_format_;

@ -26,10 +26,9 @@ struct StreamInfo {
// stream's buffer.
std::size_t bytes_in_stream{0};
// The total length of this stream, in case its source is finite (e.g. a
// file on disk). May be absent for endless streams (internet streams,
// generated audio, etc.)
std::optional<std::size_t> length_bytes{};
bool is_producer_finished = true;
bool is_consumer_finished = true;
//
std::optional<uint32_t> seek_to_seconds{};
@ -62,10 +61,8 @@ class RawStream {
public:
StreamInfo* info;
cpp::span<std::byte> data;
bool is_incomplete;
RawStream(StreamInfo* i, cpp::span<std::byte> d)
: info(i), data(d), is_incomplete(false) {}
RawStream(StreamInfo* i, cpp::span<std::byte> d) : info(i), data(d) {}
};
/*
@ -78,7 +75,9 @@ class InputStream {
void consume(std::size_t bytes) const;
void mark_incomplete() const;
bool is_producer_finished() const;
void mark_consumer_finished() const;
const StreamInfo& info() const;
@ -100,7 +99,9 @@ class OutputStream {
cpp::span<std::byte> data() const;
bool is_incomplete() const;
bool is_consumer_finished() const;
void mark_producer_finished() const;
private:
RawStream* raw_;

@ -28,8 +28,12 @@ void InputStream::consume(std::size_t bytes) const {
raw_->info->bytes_in_stream = new_data.size_bytes();
}
void InputStream::mark_incomplete() const {
raw_->is_incomplete = true;
void InputStream::mark_consumer_finished() const {
raw_->info->is_consumer_finished = true;
}
bool InputStream::is_producer_finished() const {
return raw_->info->is_producer_finished;
}
const StreamInfo& InputStream::info() const {
@ -46,17 +50,12 @@ void OutputStream::add(std::size_t bytes) const {
}
bool OutputStream::prepare(const StreamInfo::Format& new_format) {
if (std::holds_alternative<std::monostate>(raw_->info->format)) {
raw_->info->format = new_format;
raw_->info->bytes_in_stream = 0;
return true;
}
if (new_format == raw_->info->format) {
return true;
}
if (raw_->is_incomplete) {
if (std::holds_alternative<std::monostate>(raw_->info->format) ||
raw_->info->is_consumer_finished) {
raw_->info->format = new_format;
raw_->info->bytes_in_stream = 0;
raw_->info->is_producer_finished = false;
raw_->info->is_consumer_finished = false;
return true;
}
return false;
@ -70,8 +69,12 @@ cpp::span<std::byte> OutputStream::data() const {
return raw_->data.subspan(raw_->info->bytes_in_stream);
}
bool OutputStream::is_incomplete() const {
return raw_->is_incomplete;
void OutputStream::mark_producer_finished() const {
raw_->info->is_producer_finished = true;
}
bool OutputStream::is_consumer_finished() const {
return raw_->info->is_consumer_finished;
}
} // namespace audio

@ -44,6 +44,7 @@ MadMp3Decoder::~MadMp3Decoder() {
}
auto MadMp3Decoder::GetInputPosition() -> std::size_t {
assert(stream_.next_frame >= stream_.buffer);
return stream_.next_frame - stream_.buffer;
}
@ -51,7 +52,7 @@ auto MadMp3Decoder::BeginStream(const cpp::span<const std::byte> input)
-> Result<OutputFormat> {
mad_stream_buffer(&stream_,
reinterpret_cast<const unsigned char*>(input.data()),
input.size());
input.size_bytes());
// Whatever was last synthesized is now invalid, so ensure we don't try to
// send it.
current_sample_ = -1;
@ -65,11 +66,11 @@ auto MadMp3Decoder::BeginStream(const cpp::span<const std::byte> input)
// Recoverable errors are usually malformed parts of the stream.
// We can recover from them by just retrying the decode.
continue;
} else {
// 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.
return {GetInputPosition(), cpp::fail(Error::kMalformedData)};
}
if (stream_.error == MAD_ERROR_BUFLEN) {
return {GetInputPosition(), cpp::fail(Error::kOutOfInput)};
}
return {GetInputPosition(), cpp::fail(Error::kMalformedData)};
}
uint8_t channels = MAD_NCHANNELS(&header);

@ -38,7 +38,6 @@ void Running::entry() {
vTaskDelay(pdMS_TO_TICKS(250));
ESP_LOGI(kTag, "opening database");
database::Database::Destroy();
auto database_res = database::Database::Open();
if (database_res.has_error()) {
ESP_LOGW(kTag, "failed to open!");

Loading…
Cancel
Save