diff options
| author | jacqueline <me@jacqueline.id.au> | 2023-06-15 10:33:46 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2023-06-15 10:33:46 +1000 |
| commit | a2c1dfbabddc2b4abaf8bf27c9ed9d1b99594859 (patch) | |
| tree | 47fa6b1a0d4d6f1160b8d60d04ee9f6d67e10ce4 /src/audio | |
| parent | 1238437717a49924cb45a12b934b3108c402e864 (diff) | |
| download | tangara-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.cpp | 125 | ||||
| -rw-r--r-- | src/audio/audio_task.cpp | 2 | ||||
| -rw-r--r-- | src/audio/fatfs_audio_input.cpp | 8 | ||||
| -rw-r--r-- | src/audio/include/audio_decoder.hpp | 1 | ||||
| -rw-r--r-- | src/audio/include/stream_info.hpp | 4 |
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; |
