diff --git a/src/audio/CMakeLists.txt b/src/audio/CMakeLists.txt index de6c9b64..df5622f5 100644 --- a/src/audio/CMakeLists.txt +++ b/src/audio/CMakeLists.txt @@ -3,8 +3,8 @@ # SPDX-License-Identifier: GPL-3.0-only idf_component_register( - SRCS "audio_task.cpp" "fatfs_audio_input.cpp" "i2s_audio_output.cpp" - "track_queue.cpp" "audio_fsm.cpp" "sink_mixer.cpp" "resample.cpp" + SRCS "audio_decoder.cpp" "fatfs_audio_input.cpp" "i2s_audio_output.cpp" + "track_queue.cpp" "audio_fsm.cpp" "audio_converter.cpp" "resample.cpp" "fatfs_source.cpp" "bt_audio_output.cpp" INCLUDE_DIRS "include" REQUIRES "codecs" "drivers" "cbor" "result" "tasks" "span" "memory" "tinyfsm" diff --git a/src/audio/sink_mixer.cpp b/src/audio/audio_converter.cpp similarity index 92% rename from src/audio/sink_mixer.cpp rename to src/audio/audio_converter.cpp index ad7198dc..c540d821 100644 --- a/src/audio/sink_mixer.cpp +++ b/src/audio/audio_converter.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-only */ -#include "sink_mixer.hpp" +#include "audio_converter.hpp" #include #include @@ -28,7 +28,7 @@ static constexpr std::size_t kSampleBufferLength = 240 * 2; namespace audio { -SinkMixer::SinkMixer() +SampleConverter::SampleConverter() : commands_(xQueueCreate(1, sizeof(Args))), resampler_(nullptr), source_(xStreamBufferCreateWithCaps(kSourceBufferLength, @@ -49,21 +49,21 @@ SinkMixer::SinkMixer() tasks::StartPersistent([&]() { Main(); }); } -SinkMixer::~SinkMixer() { +SampleConverter::~SampleConverter() { vQueueDelete(commands_); vStreamBufferDelete(source_); } -auto SinkMixer::SetOutput(std::shared_ptr output) -> void { +auto SampleConverter::SetOutput(std::shared_ptr output) -> void { // FIXME: We should add synchronisation here, but we should be careful about // not impacting performance given that the output will change only very // rarely (if ever). sink_ = output; } -auto SinkMixer::MixAndSend(cpp::span input, - const IAudioOutput::Format& format, - bool is_eos) -> void { +auto SampleConverter::ConvertSamples(cpp::span input, + const IAudioOutput::Format& format, + bool is_eos) -> void { Args args{ .format = format, .samples_available = input.size(), @@ -81,7 +81,7 @@ auto SinkMixer::MixAndSend(cpp::span input, } } -auto SinkMixer::Main() -> void { +auto SampleConverter::Main() -> void { for (;;) { Args args; while (!xQueueReceive(commands_, &args, portMAX_DELAY)) { @@ -149,8 +149,8 @@ auto SinkMixer::Main() -> void { } } -auto SinkMixer::HandleSamples(cpp::span input, bool is_eos) - -> size_t { +auto SampleConverter::HandleSamples(cpp::span input, + bool is_eos) -> size_t { if (source_format_ == target_format_) { // The happiest possible case: the input format matches the output // format already. diff --git a/src/audio/audio_task.cpp b/src/audio/audio_decoder.cpp similarity index 81% rename from src/audio/audio_task.cpp rename to src/audio/audio_decoder.cpp index 99b1c170..03f81124 100644 --- a/src/audio/audio_task.cpp +++ b/src/audio/audio_decoder.cpp @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-only */ -#include "audio_task.hpp" +#include "audio_decoder.hpp" #include #include @@ -28,6 +28,7 @@ #include "freertos/ringbuf.h" #include "span.hpp" +#include "audio_converter.hpp" #include "audio_events.hpp" #include "audio_fsm.hpp" #include "audio_sink.hpp" @@ -36,7 +37,6 @@ #include "event_queue.hpp" #include "fatfs_audio_input.hpp" #include "sample.hpp" -#include "sink_mixer.hpp" #include "tasks.hpp" #include "track.hpp" #include "types.hpp" @@ -76,23 +76,27 @@ auto Timer::AddSamples(std::size_t samples) -> void { } } -auto AudioTask::Start(std::shared_ptr source, - std::shared_ptr sink) -> AudioTask* { - AudioTask* task = new AudioTask(source, sink); +auto Decoder::Start(std::shared_ptr source, + std::shared_ptr sink) -> Decoder* { + Decoder* task = new Decoder(source, sink); tasks::StartPersistent([=]() { task->Main(); }); return task; } -AudioTask::AudioTask(std::shared_ptr source, - std::shared_ptr mixer) - : source_(source), mixer_(mixer), codec_(), timer_(), current_format_() { +Decoder::Decoder(std::shared_ptr source, + std::shared_ptr mixer) + : source_(source), + converter_(mixer), + codec_(), + timer_(), + current_format_() { codec_buffer_ = { reinterpret_cast(heap_caps_calloc( kCodecBufferLength, sizeof(sample::Sample), MALLOC_CAP_SPIRAM)), kCodecBufferLength}; } -void AudioTask::Main() { +void Decoder::Main() { for (;;) { if (source_->HasNewStream() || !stream_) { std::shared_ptr new_stream = source_->NextStream(); @@ -110,7 +114,7 @@ void AudioTask::Main() { } } -auto AudioTask::BeginDecoding(std::shared_ptr stream) -> bool { +auto Decoder::BeginDecoding(std::shared_ptr stream) -> bool { codec_.reset(codecs::CreateCodecForType(stream->type()).value_or(nullptr)); if (!codec_) { ESP_LOGE(kTag, "no codec found"); @@ -140,15 +144,16 @@ auto AudioTask::BeginDecoding(std::shared_ptr stream) -> bool { return true; } -auto AudioTask::ContinueDecoding() -> bool { +auto Decoder::ContinueDecoding() -> bool { auto res = codec_->DecodeTo(codec_buffer_); if (res.has_error()) { return true; } if (res->samples_written > 0) { - mixer_->MixAndSend(codec_buffer_.first(res->samples_written), - current_sink_format_.value(), res->is_stream_finished); + converter_->ConvertSamples(codec_buffer_.first(res->samples_written), + current_sink_format_.value(), + res->is_stream_finished); } if (timer_) { diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp index 1ea670af..e68eedaf 100644 --- a/src/audio/audio_fsm.cpp +++ b/src/audio/audio_fsm.cpp @@ -15,8 +15,9 @@ #include "freertos/portmacro.h" #include "freertos/projdefs.h" +#include "audio_converter.hpp" +#include "audio_decoder.hpp" #include "audio_events.hpp" -#include "audio_task.hpp" #include "bluetooth.hpp" #include "bt_audio_output.hpp" #include "event_queue.hpp" @@ -24,7 +25,6 @@ #include "future_fetcher.hpp" #include "i2s_audio_output.hpp" #include "i2s_dac.hpp" -#include "sink_mixer.hpp" #include "system_events.hpp" #include "track.hpp" #include "track_queue.hpp" @@ -37,10 +37,9 @@ drivers::IGpios* AudioState::sIGpios; std::shared_ptr AudioState::sDac; std::weak_ptr AudioState::sDatabase; -std::unique_ptr AudioState::sTask; - std::shared_ptr AudioState::sFileSource; -std::shared_ptr AudioState::sMixer; +std::unique_ptr AudioState::sDecoder; +std::shared_ptr AudioState::sSampleConverter; std::shared_ptr AudioState::sOutput; TrackQueue* AudioState::sTrackQueue; @@ -65,10 +64,10 @@ auto AudioState::Init(drivers::IGpios* gpio_expander, sOutput.reset(new I2SAudioOutput(sIGpios, sDac)); // sOutput.reset(new BluetoothAudioOutput(bluetooth)); - sMixer.reset(new SinkMixer()); - sMixer->SetOutput(sOutput); + sSampleConverter.reset(new SampleConverter()); + sSampleConverter->SetOutput(sOutput); - AudioTask::Start(sFileSource, sMixer); + Decoder::Start(sFileSource, sSampleConverter); return true; } diff --git a/src/audio/include/sink_mixer.hpp b/src/audio/include/audio_converter.hpp similarity index 69% rename from src/audio/include/sink_mixer.hpp rename to src/audio/include/audio_converter.hpp index d046f835..81532969 100644 --- a/src/audio/include/sink_mixer.hpp +++ b/src/audio/include/audio_converter.hpp @@ -18,19 +18,21 @@ namespace audio { /* - * Handles the final downmix + resample + quantisation stage of audio, - * generation sending the result directly to an IAudioOutput. + * Handle to a persistent task that converts samples between formats (sample + * rate, channels, bits per sample), in order to put samples in the preferred + * format of the current output device. The resulting samples are forwarded + * to the output device's sink stream. */ -class SinkMixer { +class SampleConverter { public: - SinkMixer(); - ~SinkMixer(); + SampleConverter(); + ~SampleConverter(); auto SetOutput(std::shared_ptr) -> void; - auto MixAndSend(cpp::span, - const IAudioOutput::Format& format, - bool is_eos) -> void; + auto ConvertSamples(cpp::span, + const IAudioOutput::Format& format, + bool is_eos) -> void; private: auto Main() -> void; diff --git a/src/audio/include/audio_task.hpp b/src/audio/include/audio_decoder.hpp similarity index 63% rename from src/audio/include/audio_task.hpp rename to src/audio/include/audio_decoder.hpp index 08c5769c..1759f6e4 100644 --- a/src/audio/include/audio_task.hpp +++ b/src/audio/include/audio_decoder.hpp @@ -9,15 +9,18 @@ #include #include +#include "audio_converter.hpp" #include "audio_sink.hpp" #include "audio_source.hpp" #include "codec.hpp" -#include "sink_mixer.hpp" #include "track.hpp" #include "types.hpp" namespace audio { +/* + * Sample-based timer for the current elapsed playback time. + */ class Timer { public: Timer(const codecs::ICodec::OutputFormat& format); @@ -32,25 +35,30 @@ class Timer { uint32_t total_duration_seconds_; }; -class AudioTask { +/* + * Handle to a persistent task that takes bytes from the given source, decodes + * them into sample::Sample (normalised to 16 bit signed PCM), and then + * forwards the resulting stream to the given converter. + */ +class Decoder { public: static auto Start(std::shared_ptr source, - std::shared_ptr mixer) -> AudioTask*; + std::shared_ptr converter) -> Decoder*; auto Main() -> void; - AudioTask(const AudioTask&) = delete; - AudioTask& operator=(const AudioTask&) = delete; + Decoder(const Decoder&) = delete; + Decoder& operator=(const Decoder&) = delete; private: - AudioTask(std::shared_ptr source, - std::shared_ptr mixer); + Decoder(std::shared_ptr source, + std::shared_ptr converter); auto BeginDecoding(std::shared_ptr) -> bool; auto ContinueDecoding() -> bool; std::shared_ptr source_; - std::shared_ptr mixer_; + std::shared_ptr converter_; std::shared_ptr stream_; std::unique_ptr codec_; diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp index 6c785426..430bc298 100644 --- a/src/audio/include/audio_fsm.hpp +++ b/src/audio/include/audio_fsm.hpp @@ -13,8 +13,8 @@ #include "audio_sink.hpp" #include "tinyfsm.hpp" +#include "audio_decoder.hpp" #include "audio_events.hpp" -#include "audio_task.hpp" #include "bt_audio_output.hpp" #include "database.hpp" #include "display.hpp" @@ -68,9 +68,9 @@ class AudioState : public tinyfsm::Fsm { static std::shared_ptr sDac; static std::weak_ptr sDatabase; - static std::unique_ptr sTask; static std::shared_ptr sFileSource; - static std::shared_ptr sMixer; + static std::unique_ptr sDecoder; + static std::shared_ptr sSampleConverter; static std::shared_ptr sOutput; static TrackQueue* sTrackQueue;