summaryrefslogtreecommitdiff
path: root/src/audio
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-06-15 10:33:46 +1000
committerjacqueline <me@jacqueline.id.au>2023-06-15 10:33:46 +1000
commita2c1dfbabddc2b4abaf8bf27c9ed9d1b99594859 (patch)
tree47fa6b1a0d4d6f1160b8d60d04ee9f6d67e10ce4 /src/audio
parent1238437717a49924cb45a12b934b3108c402e864 (diff)
downloadtangara-fw-a2c1dfbabddc2b4abaf8bf27c9ed9d1b99594859.tar.gz
Add vorbis and flac decoders, flesh out codec interface
vorbis doesn't quite work yet, not sure why. will pick it up again later.
Diffstat (limited to 'src/audio')
-rw-r--r--src/audio/audio_decoder.cpp125
-rw-r--r--src/audio/audio_task.cpp2
-rw-r--r--src/audio/fatfs_audio_input.cpp8
-rw-r--r--src/audio/include/audio_decoder.hpp1
-rw-r--r--src/audio/include/stream_info.hpp4
5 files changed, 89 insertions, 51 deletions
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 <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,68 +85,94 @@ 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.
+ 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;
+ }
}
}
diff --git a/src/audio/audio_task.cpp b/src/audio/audio_task.cpp
index 9dd7d994..eea84e45 100644
--- a/src/audio/audio_task.cpp
+++ b/src/audio/audio_task.cpp
@@ -126,7 +126,7 @@ void AudioTaskMain(std::unique_ptr<Pipeline> pipeline, IAudioSink* sink) {
if (sink_stream.info().bytes_in_stream == 0) {
// No new bytes to sink, so skip sinking completely.
- ESP_LOGI(kTag, "no bytes to sink");
+ ESP_LOGW(kTag, "no bytes to sink");
continue;
}
diff --git a/src/audio/fatfs_audio_input.cpp b/src/audio/fatfs_audio_input.cpp
index a89858ca..eaa62ee3 100644
--- a/src/audio/fatfs_audio_input.cpp
+++ b/src/audio/fatfs_audio_input.cpp
@@ -56,11 +56,13 @@ auto FatfsAudioInput::OpenFile(const std::string& path) -> bool {
database::SongTags tags;
if (!tag_parser.ReadAndParseTags(path, &tags)) {
ESP_LOGE(kTag, "failed to read tags");
- return false;
+ tags.encoding = database::Encoding::kFlac;
+ // return false;
}
auto stream_type = ContainerToStreamType(tags.encoding);
if (!stream_type.has_value()) {
+ ESP_LOGE(kTag, "couldn't match container to stream");
return false;
}
@@ -144,8 +146,8 @@ auto FatfsAudioInput::ContainerToStreamType(database::Encoding enc)
return codecs::StreamType::kPcm;
case database::Encoding::kFlac:
return codecs::StreamType::kFlac;
- case database::Encoding::kOgg:
- return codecs::StreamType::kOgg;
+ case database::Encoding::kOgg: // Misnamed; this is Ogg Vorbis.
+ return codecs::StreamType::kVorbis;
case database::Encoding::kUnsupported:
default:
return {};
diff --git a/src/audio/include/audio_decoder.hpp b/src/audio/include/audio_decoder.hpp
index 3cda0305..4e7e127e 100644
--- a/src/audio/include/audio_decoder.hpp
+++ b/src/audio/include/audio_decoder.hpp
@@ -42,6 +42,7 @@ class AudioDecoder : public IAudioElement {
std::unique_ptr<codecs::ICodec> current_codec_;
std::optional<StreamInfo::Format> current_input_format_;
std::optional<StreamInfo::Format> current_output_format_;
+ std::optional<std::size_t> seek_to_sample_;
bool has_samples_to_send_;
bool has_input_remaining_;
diff --git a/src/audio/include/stream_info.hpp b/src/audio/include/stream_info.hpp
index 91b2f085..54b87003 100644
--- a/src/audio/include/stream_info.hpp
+++ b/src/audio/include/stream_info.hpp
@@ -6,6 +6,7 @@
#pragma once
+#include <stdint.h>
#include <cstdint>
#include <optional>
#include <string>
@@ -30,6 +31,9 @@ struct StreamInfo {
// generated audio, etc.)
std::optional<std::size_t> length_bytes{};
+ //
+ std::optional<uint32_t> seek_to_seconds{};
+
struct Encoded {
// The codec that this stream is associated with.
codecs::StreamType type;