diff --git a/src/audio/audio_converter.cpp b/src/audio/audio_converter.cpp index 60d83648..474bbd9f 100644 --- a/src/audio/audio_converter.cpp +++ b/src/audio/audio_converter.cpp @@ -23,8 +23,9 @@ static constexpr char kTag[] = "mixer"; -static constexpr std::size_t kSourceBufferLength = 8 * 1024; -static constexpr std::size_t kSampleBufferLength = 240 * 2 * 4; +static constexpr std::size_t kSampleBufferLength = 240 * 2 * 8; +static constexpr std::size_t kSourceBufferLength = + kSampleBufferLength * 2 * sizeof(sample::Sample); namespace audio { @@ -46,7 +47,7 @@ SampleConverter::SampleConverter() kSampleBufferLength, sizeof(sample::Sample), MALLOC_CAP_SPIRAM)), kSampleBufferLength}; - tasks::StartPersistent([&]() { Main(); }); + tasks::StartPersistent([&]() { Main(); }); } SampleConverter::~SampleConverter() { diff --git a/src/audio/audio_decoder.cpp b/src/audio/audio_decoder.cpp index f9f76387..f4868daa 100644 --- a/src/audio/audio_decoder.cpp +++ b/src/audio/audio_decoder.cpp @@ -46,7 +46,7 @@ namespace audio { static const char* kTag = "audio_dec"; -static constexpr std::size_t kCodecBufferLength = 240 * 4 * 4; +static constexpr std::size_t kCodecBufferLength = 240 * 4 * 16; Timer::Timer(const codecs::ICodec::OutputFormat& format) : current_seconds_(0), @@ -79,7 +79,8 @@ auto Timer::AddSamples(std::size_t samples) -> void { auto Decoder::Start(std::shared_ptr source, std::shared_ptr sink) -> Decoder* { Decoder* task = new Decoder(source, sink); - tasks::StartPersistent(1, [=]() { task->Main(); }); + tasks::StartPersistent(1, + [=]() { task->Main(); }); return task; } diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp index f5ce2957..af43c9b9 100644 --- a/src/audio/audio_fsm.cpp +++ b/src/audio/audio_fsm.cpp @@ -143,6 +143,9 @@ void Standby::react(const TogglePlayPause& ev) { void Playback::entry() { ESP_LOGI(kTag, "beginning playback"); sOutput->SetInUse(true); + + events::System().Dispatch(PlaybackStarted{}); + events::Ui().Dispatch(PlaybackStarted{}); } void Playback::exit() { @@ -153,6 +156,7 @@ void Playback::exit() { sOutput->SetInUse(false); events::System().Dispatch(PlaybackFinished{}); + events::Ui().Dispatch(PlaybackFinished{}); } void Playback::react(const QueueUpdate& ev) { diff --git a/src/audio/include/audio_events.hpp b/src/audio/include/audio_events.hpp index 7433d159..db6a3297 100644 --- a/src/audio/include/audio_events.hpp +++ b/src/audio/include/audio_events.hpp @@ -17,9 +17,7 @@ namespace audio { -struct PlaybackStarted : tinyfsm::Event { - database::Track track; -}; +struct PlaybackStarted : tinyfsm::Event {}; struct PlaybackUpdate : tinyfsm::Event { uint32_t seconds_elapsed; diff --git a/src/drivers/display.cpp b/src/drivers/display.cpp index e04de477..0664638b 100644 --- a/src/drivers/display.cpp +++ b/src/drivers/display.cpp @@ -60,19 +60,6 @@ static const int kDisplayBufferSize = (kDisplayWidth * kDisplayHeight) / 10; DMA_ATTR static lv_color_t sBuffer1[kDisplayBufferSize]; DMA_ATTR static lv_color_t sBuffer2[kDisplayBufferSize]; -struct RenderTaskArgs { - std::atomic* quit; - QueueHandle_t work_queue; -}; - -struct FlushArgs { - lv_disp_drv_t* driver; - const lv_area_t* area; - lv_color_t* color_map; -}; - -void RenderMain(void* raw_args); - namespace drivers { /* @@ -81,8 +68,10 @@ namespace drivers { extern "C" void FlushDataCallback(lv_disp_drv_t* disp_drv, const lv_area_t* area, lv_color_t* color_map) { + taskYIELD(); Display* instance = static_cast(disp_drv->user_data); instance->OnLvglFlush(disp_drv, area, color_map); + taskYIELD(); } auto Display::Create(IGpios& expander, @@ -155,7 +144,6 @@ auto Display::Create(IGpios& expander, auto display = std::make_unique(expander, handle); // Now we reset the display into a known state, then configure it - // TODO(jacqueline): set rotation ESP_LOGI(kTag, "Sending init sequences"); for (int i = 0; i < init_data.num_sequences; i++) { display->SendInitialisationSequence(init_data.sequences[i]); @@ -183,13 +171,7 @@ auto Display::Create(IGpios& expander, } Display::Display(IGpios& gpio, spi_device_handle_t handle) - : gpio_(gpio), - handle_(handle), - worker_task_(tasks::Worker::Start()), - display_on_(false), - brightness_(0) { - SetBrightness(50); -} + : gpio_(gpio), handle_(handle), display_on_(false), brightness_(0) {} Display::~Display() { ledc_fade_func_uninstall(); @@ -303,10 +285,6 @@ void Display::SendTransaction(TransactionType type, void Display::OnLvglFlush(lv_disp_drv_t* disp_drv, const lv_area_t* area, lv_color_t* color_map) { - // area is stack-allocated, so it isn't safe to reference from the flush - // thread. - lv_area_t area_copy = *area; - // worker_task_->Dispatch([=, this]() { // Ideally we want to complete a single flush as quickly as possible, so // grab the bus for this entire transaction sequence. spi_device_acquire_bus(handle_, portMAX_DELAY); @@ -314,13 +292,13 @@ void Display::OnLvglFlush(lv_disp_drv_t* disp_drv, // First we need to specify the rectangle of the display we're writing into. uint16_t data[2] = {0, 0}; - data[0] = SPI_SWAP_DATA_TX(area_copy.x1, 16); - data[1] = SPI_SWAP_DATA_TX(area_copy.x2, 16); + data[0] = SPI_SWAP_DATA_TX(area->x1, 16); + data[1] = SPI_SWAP_DATA_TX(area->x2, 16); SendCommandWithData(displays::ST77XX_CASET, reinterpret_cast(data), 4); - data[0] = SPI_SWAP_DATA_TX(area_copy.y1, 16); - data[1] = SPI_SWAP_DATA_TX(area_copy.y2, 16); + data[0] = SPI_SWAP_DATA_TX(area->y1, 16); + data[1] = SPI_SWAP_DATA_TX(area->y2, 16); SendCommandWithData(displays::ST77XX_RASET, reinterpret_cast(data), 4); @@ -332,22 +310,6 @@ void Display::OnLvglFlush(lv_disp_drv_t* disp_drv, spi_device_release_bus(handle_); lv_disp_flush_ready(&driver_); - //}); -} - -void RenderMain(void* raw_args) { - RenderTaskArgs* args = reinterpret_cast(raw_args); - QueueHandle_t queue = args->work_queue; - std::atomic* quit = args->quit; - delete args; - - while (!quit->load()) { - // TODO: flush data here! Yay speed. - } - - vQueueDelete(queue); - delete quit; - vTaskDelete(NULL); } } // namespace drivers diff --git a/src/drivers/include/display.hpp b/src/drivers/include/display.hpp index 766fc4ea..b0aa5d58 100644 --- a/src/drivers/include/display.hpp +++ b/src/drivers/include/display.hpp @@ -52,8 +52,6 @@ class Display { IGpios& gpio_; spi_device_handle_t handle_; - std::unique_ptr worker_task_; - bool display_on_; uint_fast8_t brightness_; diff --git a/src/tasks/tasks.cpp b/src/tasks/tasks.cpp index f7dc0279..ae7b9eb2 100644 --- a/src/tasks/tasks.cpp +++ b/src/tasks/tasks.cpp @@ -19,44 +19,36 @@ auto Name() -> std::string; template <> auto Name() -> std::string { - return "LVGL"; + return "ui"; } template <> -auto Name() -> std::string { - return "DISPLAY"; +auto Name() -> std::string { + return "audio_dec"; } template <> -auto Name() -> std::string { - return "FSTREAMER"; -} -template <> -auto Name() -> std::string { - return "AUDIO"; -} -template <> -auto Name() -> std::string { - return "MIXER"; +auto Name() -> std::string { + return "audio_conv"; } template <> auto Name() -> std::string { - return "DB"; + return "db_fg"; } template <> auto Name() -> std::string { - return "DB_BG"; + return "db_bg"; } template <> auto Name() -> std::string { - return "NVS"; + return "nvs"; } template auto AllocateStack() -> cpp::span; -// Decoders run on the audio task, and these sometimes require a fairly large -// amount of stack space. +// Decoders often require a very large amount of stack space, since they aren't +// usually written with embedded use cases in mind. template <> -auto AllocateStack() -> cpp::span { +auto AllocateStack() -> cpp::span { std::size_t size = 64 * 1024; return {static_cast(heap_caps_malloc(size, MALLOC_CAP_SPIRAM)), size}; @@ -69,29 +61,15 @@ auto AllocateStack() -> cpp::span { return {static_cast(heap_caps_malloc(size, MALLOC_CAP_SPIRAM)), size}; } -// UI flushes *must* be done from internal RAM. Thankfully, there is very little -// stack required to perform them, and the amount of stack needed is fixed. template <> -auto AllocateStack() -> cpp::span { - std::size_t size = 1024; - return {static_cast(heap_caps_malloc(size, MALLOC_CAP_DEFAULT)), - size}; -} - -template <> -auto AllocateStack() -> cpp::span { +// PCM conversion and resampling uses a very small amount of stack. It works +// entirely with PSRAM-allocated buffers, so no real speed gain from allocating +// it internally. +auto AllocateStack() -> cpp::span { std::size_t size = 4 * 1024; return {static_cast(heap_caps_malloc(size, MALLOC_CAP_SPIRAM)), size}; } - -template <> -auto AllocateStack() -> cpp::span { - std::size_t size = 4 * 1024; - return {static_cast(heap_caps_malloc(size, MALLOC_CAP_SPIRAM)), - size}; -} - // Leveldb is designed for non-embedded use cases, where stack space isn't so // much of a concern. It therefore uses an eye-wateringly large amount of stack. template <> @@ -114,8 +92,8 @@ auto AllocateStack() -> cpp::span { size}; } -// 2048 bytes in internal ram -// 302 KiB in external ram. +// 2 KiB in internal ram +// 612 KiB in external ram. /* * Please keep the priorities below in descending order for better readability. @@ -124,39 +102,22 @@ auto AllocateStack() -> cpp::span { template auto Priority() -> UBaseType_t; -// NVS writing requires suspending one of our cores, and disabling tasks with -// their stacks in PSRAM. Get it over and done with as soon as possible. -template <> -auto Priority() -> UBaseType_t { - return 13; -} -// Realtime audio is the entire point of this device, so give this task the +// Realtime audio is the entire point of this device, so give these tasks the // highest priority. template <> -auto Priority() -> UBaseType_t { - return 12; +auto Priority() -> UBaseType_t { + return 18; } template <> -auto Priority() -> UBaseType_t { - return 11; +auto Priority() -> UBaseType_t { + return 18; } // After audio issues, UI jank is the most noticeable kind of scheduling-induced // slowness that the user is likely to notice or care about. Therefore we place -// this task directly below audio in terms of priority. +// this task directly below audio in terms of priority. Note that during audio +// playback, this priority will be downgraded. template <> auto Priority() -> UBaseType_t { - return 9; -} -// UI flushing should use the same priority as the UI task, so as to maximise -// the chance of the happy case: one of our cores is writing to the screen, -// whilst the other is simultaneously preparing the next buffer to be flushed. -template <> -auto Priority() -> UBaseType_t { - return 9; -} - -template <> -auto Priority() -> UBaseType_t { return 10; } // Database interactions are all inherently async already, due to their @@ -165,11 +126,18 @@ auto Priority() -> UBaseType_t { // priority. template <> auto Priority() -> UBaseType_t { - return 8; + return 2; } template <> auto Priority() -> UBaseType_t { - return 7; + return 1; +} +// NVS writing requires suspending one of our cores, and disabling tasks with +// their stacks in PSRAM. Only do it when there's not more important work +// pending. +template <> +auto Priority() -> UBaseType_t { + return 2; } template @@ -184,10 +152,6 @@ auto WorkerQueueSize() -> std::size_t { return 8; } -template <> -auto WorkerQueueSize() -> std::size_t { - return 2; -} template <> auto WorkerQueueSize() -> std::size_t { return 2; diff --git a/src/tasks/tasks.hpp b/src/tasks/tasks.hpp index 17836795..2e43b01a 100644 --- a/src/tasks/tasks.hpp +++ b/src/tasks/tasks.hpp @@ -30,18 +30,17 @@ namespace tasks { enum class Type { // The main UI task. This runs the LVGL main loop. kUi, - // Task for flushing graphics buffers to the display. - kUiFlush, - // TODO. - kFileStreamer, - // The main audio pipeline task. - kAudio, - // TODO - kMixer, + // The main audio pipeline task. Decodes files into PCM stream. + kAudioDecoder, + // Second audio task. Converts the PCM stream into one suitable for the + // current output (e.g. downsampling for bluetooth). + kAudioConverter, // Task for running database queries. kDatabase, // Task for internal database operations kDatabaseBackground, + // Task for interacting with NVS -- this needs to be done with an internal + // stack. kNvsWriter, }; diff --git a/src/ui/include/lvgl_task.hpp b/src/ui/include/lvgl_task.hpp index 6b7e446e..4362249b 100644 --- a/src/ui/include/lvgl_task.hpp +++ b/src/ui/include/lvgl_task.hpp @@ -40,10 +40,6 @@ class UiTask { std::shared_ptr input_device_; std::shared_ptr current_screen_; - - std::atomic quit_; - SemaphoreHandle_t frame_semaphore_; - TimerHandle_t frame_timer_; }; } // namespace ui diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp index 12fe5c69..7ac9c7b6 100644 --- a/src/ui/include/ui_fsm.hpp +++ b/src/ui/include/ui_fsm.hpp @@ -48,10 +48,9 @@ class UiState : public tinyfsm::Fsm { void react(const tinyfsm::Event& ev) {} virtual void react(const system_fsm::BatteryStateChanged&); - - virtual void react(const audio::PlaybackStarted&) {} + virtual void react(const audio::PlaybackStarted&); + virtual void react(const audio::PlaybackFinished&); virtual void react(const audio::PlaybackUpdate&) {} - virtual void react(const audio::PlaybackFinished&) {} virtual void react(const audio::QueueUpdate&) {} virtual void react(const system_fsm::KeyLockChanged&); diff --git a/src/ui/lvgl_task.cpp b/src/ui/lvgl_task.cpp index a0f14f7d..74f68cf5 100644 --- a/src/ui/lvgl_task.cpp +++ b/src/ui/lvgl_task.cpp @@ -49,24 +49,8 @@ namespace ui { static const char* kTag = "ui_task"; -static const TickType_t kMaxFrameRate = pdMS_TO_TICKS(100); -static auto next_frame(TimerHandle_t t) { - SemaphoreHandle_t sem = - reinterpret_cast(pvTimerGetTimerID(t)); - xSemaphoreGive(sem); -} - -UiTask::UiTask() - : quit_(false), - frame_semaphore_(xSemaphoreCreateBinary()), - frame_timer_(xTimerCreate("ui_frame", - kMaxFrameRate, - pdTRUE, - frame_semaphore_, - next_frame)) { - xTimerStart(frame_timer_, portMAX_DELAY); -} +UiTask::UiTask() {} UiTask::~UiTask() { assert(false); @@ -98,10 +82,8 @@ auto UiTask::Main() -> void { current_screen_->Tick(); } - lv_task_handler(); - - // Wait for the signal to loop again. - xSemaphoreTake(frame_semaphore_, portMAX_DELAY); + TickType_t delay = lv_timer_handler(); + vTaskDelay(pdMS_TO_TICKS(std::clamp(delay, 0, 100))); } } @@ -114,7 +96,7 @@ auto UiTask::SetInputDevice(std::shared_ptr dev) -> void { auto UiTask::Start() -> UiTask* { UiTask* ret = new UiTask(); - tasks::StartPersistent(0, [=]() { ret->Main(); }); + tasks::StartPersistent([=]() { ret->Main(); }); return ret; } diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index e874418b..c463933f 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -90,6 +90,13 @@ void UiState::react(const system_fsm::BatteryStateChanged&) { UpdateTopBar(); } +void UiState::react(const audio::PlaybackStarted&) { + vTaskPrioritySet(NULL, 0); +} +void UiState::react(const audio::PlaybackFinished&) { + vTaskPrioritySet(NULL, 10); +} + void UiState::UpdateTopBar() { auto battery_state = sServices->battery().State(); bool has_queue = sServices->track_queue().GetCurrent().has_value(); @@ -251,6 +258,7 @@ void Playing::exit() { } void Playing::react(const audio::PlaybackStarted& ev) { + vTaskPrioritySet(NULL, 0); UpdateTopBar(); sPlayingScreen->OnTrackUpdate(); } @@ -261,6 +269,7 @@ void Playing::react(const audio::PlaybackUpdate& ev) { void Playing::react(const audio::PlaybackFinished& ev) { UpdateTopBar(); + vTaskPrioritySet(NULL, 10); } void Playing::react(const audio::QueueUpdate& ev) {