/* * Copyright 2023 jacqueline * * SPDX-License-Identifier: GPL-3.0-only */ #include "audio_task.hpp" #include #include #include #include #include #include #include #include #include #include "audio_decoder.hpp" #include "audio_events.hpp" #include "audio_fsm.hpp" #include "audio_sink.hpp" #include "cbor.h" #include "codec.hpp" #include "esp_err.h" #include "esp_heap_caps.h" #include "esp_log.h" #include "event_queue.hpp" #include "fatfs_audio_input.hpp" #include "freertos/portmacro.h" #include "freertos/projdefs.h" #include "freertos/queue.h" #include "freertos/ringbuf.h" #include "pipeline.hpp" #include "span.hpp" #include "arena.hpp" #include "audio_element.hpp" #include "chunk.hpp" #include "stream_event.hpp" #include "stream_info.hpp" #include "stream_message.hpp" #include "sys/_stdint.h" #include "tasks.hpp" #include "ui_fsm.hpp" namespace audio { static const char* kTag = "audio_dec"; static constexpr std::size_t kSampleBufferSize = 16 * 1024; Timer::Timer(StreamInfo::Pcm format) : format_(format), current_seconds_(0), current_sample_in_second_(0), total_duration_seconds_(0) {} auto Timer::SetLengthSeconds(uint32_t len) -> void { total_duration_seconds_ = len; } auto Timer::SetLengthBytes(uint32_t len) -> void { total_duration_seconds_ = 0; } auto Timer::AddBytes(std::size_t bytes) -> void { float samples_sunk = bytes; samples_sunk /= format_.channels; // Samples must be aligned to 16 bits. The number of actual bytes per // sample is therefore the bps divided by 16, rounded up (align to word), // times two (convert to bytes). uint8_t bytes_per_sample = ((format_.bits_per_sample + 16 - 1) / 16) * 2; samples_sunk /= bytes_per_sample; current_sample_in_second_ += samples_sunk; bool incremented = false; while (current_sample_in_second_ > format_.sample_rate) { current_seconds_++; current_sample_in_second_ -= format_.sample_rate; incremented = true; } if (incremented) { // ESP_LOGI("timer", "new time %lu", current_seconds_); events::Audio().Dispatch(PlaybackUpdate{ .seconds_elapsed = current_seconds_, .seconds_total = 0, }); } } auto AudioTask::Start(IAudioSource* source, IAudioSink* sink) -> AudioTask* { AudioTask* task = new AudioTask(source, sink); tasks::StartPersistent([=]() { task->Main(); }); return task; } AudioTask::AudioTask(IAudioSource* source, IAudioSink* sink) : source_(source), sink_(sink), codec_(), timer_(), is_new_stream_(false), current_input_format_(), current_output_format_(), sample_buffer_(reinterpret_cast( heap_caps_malloc(kSampleBufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT))), sample_buffer_len_(kSampleBufferSize) {} void AudioTask::Main() { for (;;) { source_->Read( [this](StreamInfo::Format format) -> bool { if (current_input_format_ && format == *current_input_format_) { // This is the continuation of previous data. We can handle it if // we are able to decode it, or if it doesn't need decoding. return current_output_format_ == format || codec_ != nullptr; } // This must be a new stream of data. Reset everything to prepare to // handle it. current_input_format_ = format; is_new_stream_ = true; codec_.reset(); timer_.reset(); // What kind of data does this new stream contain? if (std::holds_alternative(format)) { // It's already decoded! We can handle this immediately if it // matches what we're currently sending to the sink. Otherwise, we // will need to wait for the sink to drain before we can reconfigure // it. if (current_output_format_ && format == *current_output_format_) { return true; } else if (xStreamBufferIsEmpty(sink_->stream())) { return true; } else { return false; } } else if (std::holds_alternative(format)) { // The stream has some kind of encoding. Whether or not we can // handle it is entirely down to whether or not we have a codec for // it. auto encoding = std::get(format); auto codec = codecs::CreateCodecForType(encoding.type); if (codec) { ESP_LOGI(kTag, "successfully created codec for stream"); codec_.reset(*codec); return true; } else { ESP_LOGE(kTag, "stream has unknown encoding"); return false; } } else { // programmer error / skill issue :( ESP_LOGE(kTag, "stream has unknown format"); current_input_format_ = format; return false; } }, [this](cpp::span bytes) -> size_t { // PCM streams are simple, so handle them first. if (std::holds_alternative(*current_input_format_)) { // First we need to reconfigure the sink for this sample format. // TODO(jacqueline): We should verify whether or not the sink can // actually deal with this format first. if (current_input_format_ != current_output_format_) { current_output_format_ = current_input_format_; sink_->Configure(*current_output_format_); timer_.reset(new Timer( std::get(*current_output_format_))); } // Stream the raw samples directly to the sink. xStreamBufferSend(sink_->stream(), bytes.data(), bytes.size_bytes(), portMAX_DELAY); timer_->AddBytes(bytes.size_bytes()); return bytes.size_bytes(); } // Else, assume it's an encoded stream. size_t bytes_used = 0; if (is_new_stream_) { // This is a new stream! First order of business is verifying that // we can indeed decode it. auto res = codec_->BeginStream(bytes); bytes_used += res.first; if (res.second.has_error()) { if (res.second.error() != codecs::ICodec::Error::kOutOfInput) { // Decoding the header failed, so we can't actually deal with // this stream after all. It could be malformed. ESP_LOGE(kTag, "error beginning stream"); codec_.reset(); } return bytes_used; } is_new_stream_ = false; codecs::ICodec::OutputFormat format = res.second.value(); StreamInfo::Pcm pcm{ .channels = format.num_channels, .bits_per_sample = format.bits_per_sample, .sample_rate = format.sample_rate_hz, }; StreamInfo::Format new_format{pcm}; timer_.reset(new Timer{pcm}); if (format.duration_seconds) { timer_->SetLengthSeconds(*format.duration_seconds); } // Now that we have the output format for decoded samples from this // stream, we need to see if they are compatible with what's already // in the sink stream. if (new_format != current_output_format_) { // The new format is different to the old one. Wait for the sink // to drain before continuing. while (!xStreamBufferIsEmpty(sink_->stream())) { ESP_LOGI(kTag, "waiting for sink stream to drain..."); // TODO(jacqueline): Get the sink drain ISR to notify us of this // via semaphore instead of busy-ish waiting. vTaskDelay(pdMS_TO_TICKS(100)); } } ESP_LOGI(kTag, "configuring sink"); current_output_format_ = new_format; sink_->Configure(new_format); timer_.reset( new Timer(std::get(*current_output_format_))); } // At this point the decoder has been initialised, and the sink has // been correctly configured. All that remains is to throw samples // into the sink as fast as possible. while (bytes_used < bytes.size_bytes()) { auto res = codec_->ContinueStream(bytes.subspan(bytes_used), {sample_buffer_, sample_buffer_len_}); bytes_used += res.first; if (res.second.has_error()) { return bytes_used; } else { xStreamBufferSend(sink_->stream(), sample_buffer_, res.second->bytes_written, portMAX_DELAY); timer_->AddBytes(res.second->bytes_written); } } return bytes_used; }, portMAX_DELAY); } } } // namespace audio