summaryrefslogtreecommitdiff
path: root/src/audio/audio_decoder.cpp
diff options
context:
space:
mode:
authorAilurux <ailuruxx@gmail.com>2023-06-19 11:21:32 +1000
committerAilurux <ailuruxx@gmail.com>2023-06-19 11:21:32 +1000
commit039272455acddbe446269ea4b6ef66f44f457f1e (patch)
tree1e9a8173aeb4eb027701e89019e9410a9550d3cb /src/audio/audio_decoder.cpp
parent8ce751ad56c7efe19f835e3b6bbb1a843cef9119 (diff)
parent6ff8b5886ef91ed46dba08686900d519f6c9c62d (diff)
downloadtangara-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.cpp122
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