diff options
| author | Ailurux <ailuruxx@gmail.com> | 2023-06-19 11:21:32 +1000 |
|---|---|---|
| committer | Ailurux <ailuruxx@gmail.com> | 2023-06-19 11:21:32 +1000 |
| commit | 039272455acddbe446269ea4b6ef66f44f457f1e (patch) | |
| tree | 1e9a8173aeb4eb027701e89019e9410a9550d3cb /src/audio/audio_decoder.cpp | |
| parent | 8ce751ad56c7efe19f835e3b6bbb1a843cef9119 (diff) | |
| parent | 6ff8b5886ef91ed46dba08686900d519f6c9c62d (diff) | |
| download | tangara-fw-039272455acddbe446269ea4b6ef66f44f457f1e.tar.gz | |
Merge branch 'main' of https://git.sr.ht/~jacqueline/tangara-fw
Diffstat (limited to 'src/audio/audio_decoder.cpp')
| -rw-r--r-- | src/audio/audio_decoder.cpp | 122 |
1 files changed, 74 insertions, 48 deletions
diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp index eb19b75f..b4af65fb 100644 --- a/src/audio/audio_decoder.cpp +++ b/src/audio/audio_decoder.cpp @@ -14,6 +14,7 @@ #include <memory> #include <variant> +#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<StreamInfo::Encoded>(*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,69 +85,90 @@ auto AudioDecoder::Process(const std::vector<InputStream>& inputs, OutputStream* output) -> void { auto input = inputs.begin(); const StreamInfo& info = input->info(); - if (std::holds_alternative<std::monostate>(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. has_samples_to_send_ = false; - ProcessStreamInfo(info); + if (!ProcessStreamInfo(info)) { + return; + } + ESP_LOGI(kTag, "beginning new stream"); + 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(); + 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; - } else { - has_samples_to_send_ = true; } } - - std::size_t pos = current_codec_->GetInputPosition(); - if (pos > 0) { - input->consume(pos - 1); - } } } // namespace audio |
