From a2c1dfbabddc2b4abaf8bf27c9ed9d1b99594859 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Thu, 15 Jun 2023 10:33:46 +1000 Subject: Add vorbis and flac decoders, flesh out codec interface vorbis doesn't quite work yet, not sure why. will pick it up again later. --- src/audio/audio_decoder.cpp | 125 +++++++++++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 47 deletions(-) (limited to 'src/audio/audio_decoder.cpp') diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp index eb19b75f..310f5740 100644 --- a/src/audio/audio_decoder.cpp +++ b/src/audio/audio_decoder.cpp @@ -14,6 +14,7 @@ #include #include +#include "codec.hpp" #include "freertos/FreeRTOS.h" #include "esp_heap_caps.h" @@ -50,6 +51,9 @@ auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) -> bool { // 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(*current_input_format_); if (cur_encoding.type == encoded.type) { @@ -58,6 +62,7 @@ auto AudioDecoder::ProcessStreamInfo(const StreamInfo& info) -> bool { return true; } } + */ current_input_format_ = info.format; ESP_LOGI(kTag, "creating new decoder"); @@ -80,68 +85,94 @@ auto AudioDecoder::Process(const std::vector& inputs, OutputStream* output) -> void { auto input = inputs.begin(); const StreamInfo& info = input->info(); - if (std::holds_alternative(info.format) || - info.bytes_in_stream == 0) { - // TODO(jacqueline): should we clear the stream format? - // output->prepare({}); - return; - } + // 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) { - // The input stream has changed! Immediately throw everything away and - // start from scratch. + ESP_LOGI(kTag, "beginning new stream"); has_samples_to_send_ = false; ProcessStreamInfo(info); + auto res = current_codec_->BeginStream(input->data()); + input->consume(res.first); + if (res.second.has_error()) { + // TODO(jacqueline): Handle errors. + return; + } + + // The stream started successfully. Record what format the samples are in. + codecs::ICodec::OutputFormat format = res.second.value(); + current_output_format_ = StreamInfo::Pcm{ + .channels = format.num_channels, + .bits_per_sample = format.bits_per_sample, + .sample_rate = format.sample_rate_hz, + }; + + if (info.seek_to_seconds) { + seek_to_sample_ = *info.seek_to_seconds * format.sample_rate_hz; + } else { + seek_to_sample_.reset(); + } } - current_codec_->SetInput(input->data()); + while (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(); + } + } + has_input_remaining_ = true; while (true) { - if (has_samples_to_send_) { - auto format = current_codec_->GetOutputFormat(); - if (format.has_value()) { - current_output_format_ = StreamInfo::Pcm{ - .channels = format->num_channels, - .bits_per_sample = format->bits_per_sample, - .sample_rate = format->sample_rate_hz, - }; - - if (!output->prepare(*current_output_format_)) { - break; - } - - auto write_res = current_codec_->WriteOutputSamples(output->data()); - output->add(write_res.first); - has_samples_to_send_ = !write_res.second; - - 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; - } - } + // TODO(jacqueline): Pass through seek info here? + if (!output->prepare(*current_output_format_)) { + ESP_LOGI(kTag, "waiting for buffer to become free"); + break; } - auto res = current_codec_->ProcessNextFrame(); - if (res.has_error()) { - // TODO(jacqueline): Handle errors. + auto res = current_codec_->ContinueStream(input->data(), output->data()); + input->consume(res.first); + if (res.second.has_error()) { + if (res.second.error() == codecs::ICodec::Error::kOutOfInput) { + ESP_LOGW(kTag, "out of input"); + ESP_LOGW(kTag, "(%u bytes left)", input->data().size_bytes()); + 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(); + } else { + // TODO(jacqueline): Handle errors. + ESP_LOGE(kTag, "codec return fatal error"); + } return; } - has_input_remaining_ = !res.value(); - if (!has_input_remaining_) { - // We're out of useable data in this buffer. Finish immediately; there's - // nothing to send. - input->mark_incomplete(); - break; - } else { - has_samples_to_send_ = true; + ESP_LOGI(kTag, "enc read: %u", res.first); + + codecs::ICodec::OutputInfo out_info = res.second.value(); + output->add(out_info.bytes_written); + has_samples_to_send_ = !out_info.is_finished_writing; + + ESP_LOGI(kTag, "enc wrote: %u", out_info.bytes_written); + if (out_info.is_finished_writing) { + ESP_LOGI(kTag, "(write finished)"); } - } - std::size_t pos = current_codec_->GetInputPosition(); - if (pos > 0) { - input->consume(pos - 1); + 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; + } } } -- cgit v1.2.3