Move UI task to priority 0 during playback

Also other misc task cleanup
custom
jacqueline 2 years ago
parent 382d82a14b
commit 0032896251
  1. 7
      src/audio/audio_converter.cpp
  2. 5
      src/audio/audio_decoder.cpp
  3. 4
      src/audio/audio_fsm.cpp
  4. 4
      src/audio/include/audio_events.hpp
  5. 52
      src/drivers/display.cpp
  6. 2
      src/drivers/include/display.hpp
  7. 102
      src/tasks/tasks.cpp
  8. 15
      src/tasks/tasks.hpp
  9. 4
      src/ui/include/lvgl_task.hpp
  10. 5
      src/ui/include/ui_fsm.hpp
  11. 26
      src/ui/lvgl_task.cpp
  12. 9
      src/ui/ui_fsm.cpp

@ -23,8 +23,9 @@
static constexpr char kTag[] = "mixer"; static constexpr char kTag[] = "mixer";
static constexpr std::size_t kSourceBufferLength = 8 * 1024; static constexpr std::size_t kSampleBufferLength = 240 * 2 * 8;
static constexpr std::size_t kSampleBufferLength = 240 * 2 * 4; static constexpr std::size_t kSourceBufferLength =
kSampleBufferLength * 2 * sizeof(sample::Sample);
namespace audio { namespace audio {
@ -46,7 +47,7 @@ SampleConverter::SampleConverter()
kSampleBufferLength, sizeof(sample::Sample), MALLOC_CAP_SPIRAM)), kSampleBufferLength, sizeof(sample::Sample), MALLOC_CAP_SPIRAM)),
kSampleBufferLength}; kSampleBufferLength};
tasks::StartPersistent<tasks::Type::kMixer>([&]() { Main(); }); tasks::StartPersistent<tasks::Type::kAudioConverter>([&]() { Main(); });
} }
SampleConverter::~SampleConverter() { SampleConverter::~SampleConverter() {

@ -46,7 +46,7 @@ namespace audio {
static const char* kTag = "audio_dec"; 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) Timer::Timer(const codecs::ICodec::OutputFormat& format)
: current_seconds_(0), : current_seconds_(0),
@ -79,7 +79,8 @@ auto Timer::AddSamples(std::size_t samples) -> void {
auto Decoder::Start(std::shared_ptr<IAudioSource> source, auto Decoder::Start(std::shared_ptr<IAudioSource> source,
std::shared_ptr<SampleConverter> sink) -> Decoder* { std::shared_ptr<SampleConverter> sink) -> Decoder* {
Decoder* task = new Decoder(source, sink); Decoder* task = new Decoder(source, sink);
tasks::StartPersistent<tasks::Type::kAudio>(1, [=]() { task->Main(); }); tasks::StartPersistent<tasks::Type::kAudioDecoder>(1,
[=]() { task->Main(); });
return task; return task;
} }

@ -143,6 +143,9 @@ void Standby::react(const TogglePlayPause& ev) {
void Playback::entry() { void Playback::entry() {
ESP_LOGI(kTag, "beginning playback"); ESP_LOGI(kTag, "beginning playback");
sOutput->SetInUse(true); sOutput->SetInUse(true);
events::System().Dispatch(PlaybackStarted{});
events::Ui().Dispatch(PlaybackStarted{});
} }
void Playback::exit() { void Playback::exit() {
@ -153,6 +156,7 @@ void Playback::exit() {
sOutput->SetInUse(false); sOutput->SetInUse(false);
events::System().Dispatch(PlaybackFinished{}); events::System().Dispatch(PlaybackFinished{});
events::Ui().Dispatch(PlaybackFinished{});
} }
void Playback::react(const QueueUpdate& ev) { void Playback::react(const QueueUpdate& ev) {

@ -17,9 +17,7 @@
namespace audio { namespace audio {
struct PlaybackStarted : tinyfsm::Event { struct PlaybackStarted : tinyfsm::Event {};
database::Track track;
};
struct PlaybackUpdate : tinyfsm::Event { struct PlaybackUpdate : tinyfsm::Event {
uint32_t seconds_elapsed; uint32_t seconds_elapsed;

@ -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 sBuffer1[kDisplayBufferSize];
DMA_ATTR static lv_color_t sBuffer2[kDisplayBufferSize]; DMA_ATTR static lv_color_t sBuffer2[kDisplayBufferSize];
struct RenderTaskArgs {
std::atomic<bool>* 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 { namespace drivers {
/* /*
@ -81,8 +68,10 @@ namespace drivers {
extern "C" void FlushDataCallback(lv_disp_drv_t* disp_drv, extern "C" void FlushDataCallback(lv_disp_drv_t* disp_drv,
const lv_area_t* area, const lv_area_t* area,
lv_color_t* color_map) { lv_color_t* color_map) {
taskYIELD();
Display* instance = static_cast<Display*>(disp_drv->user_data); Display* instance = static_cast<Display*>(disp_drv->user_data);
instance->OnLvglFlush(disp_drv, area, color_map); instance->OnLvglFlush(disp_drv, area, color_map);
taskYIELD();
} }
auto Display::Create(IGpios& expander, auto Display::Create(IGpios& expander,
@ -155,7 +144,6 @@ auto Display::Create(IGpios& expander,
auto display = std::make_unique<Display>(expander, handle); auto display = std::make_unique<Display>(expander, handle);
// Now we reset the display into a known state, then configure it // Now we reset the display into a known state, then configure it
// TODO(jacqueline): set rotation
ESP_LOGI(kTag, "Sending init sequences"); ESP_LOGI(kTag, "Sending init sequences");
for (int i = 0; i < init_data.num_sequences; i++) { for (int i = 0; i < init_data.num_sequences; i++) {
display->SendInitialisationSequence(init_data.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) Display::Display(IGpios& gpio, spi_device_handle_t handle)
: gpio_(gpio), : gpio_(gpio), handle_(handle), display_on_(false), brightness_(0) {}
handle_(handle),
worker_task_(tasks::Worker::Start<tasks::Type::kUiFlush>()),
display_on_(false),
brightness_(0) {
SetBrightness(50);
}
Display::~Display() { Display::~Display() {
ledc_fade_func_uninstall(); ledc_fade_func_uninstall();
@ -303,10 +285,6 @@ void Display::SendTransaction(TransactionType type,
void Display::OnLvglFlush(lv_disp_drv_t* disp_drv, void Display::OnLvglFlush(lv_disp_drv_t* disp_drv,
const lv_area_t* area, const lv_area_t* area,
lv_color_t* color_map) { 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<void>([=, this]() {
// Ideally we want to complete a single flush as quickly as possible, so // Ideally we want to complete a single flush as quickly as possible, so
// grab the bus for this entire transaction sequence. // grab the bus for this entire transaction sequence.
spi_device_acquire_bus(handle_, portMAX_DELAY); 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. // First we need to specify the rectangle of the display we're writing into.
uint16_t data[2] = {0, 0}; uint16_t data[2] = {0, 0};
data[0] = SPI_SWAP_DATA_TX(area_copy.x1, 16); data[0] = SPI_SWAP_DATA_TX(area->x1, 16);
data[1] = SPI_SWAP_DATA_TX(area_copy.x2, 16); data[1] = SPI_SWAP_DATA_TX(area->x2, 16);
SendCommandWithData(displays::ST77XX_CASET, reinterpret_cast<uint8_t*>(data), SendCommandWithData(displays::ST77XX_CASET, reinterpret_cast<uint8_t*>(data),
4); 4);
data[0] = SPI_SWAP_DATA_TX(area_copy.y1, 16); data[0] = SPI_SWAP_DATA_TX(area->y1, 16);
data[1] = SPI_SWAP_DATA_TX(area_copy.y2, 16); data[1] = SPI_SWAP_DATA_TX(area->y2, 16);
SendCommandWithData(displays::ST77XX_RASET, reinterpret_cast<uint8_t*>(data), SendCommandWithData(displays::ST77XX_RASET, reinterpret_cast<uint8_t*>(data),
4); 4);
@ -332,22 +310,6 @@ void Display::OnLvglFlush(lv_disp_drv_t* disp_drv,
spi_device_release_bus(handle_); spi_device_release_bus(handle_);
lv_disp_flush_ready(&driver_); lv_disp_flush_ready(&driver_);
//});
}
void RenderMain(void* raw_args) {
RenderTaskArgs* args = reinterpret_cast<RenderTaskArgs*>(raw_args);
QueueHandle_t queue = args->work_queue;
std::atomic<bool>* quit = args->quit;
delete args;
while (!quit->load()) {
// TODO: flush data here! Yay speed.
}
vQueueDelete(queue);
delete quit;
vTaskDelete(NULL);
} }
} // namespace drivers } // namespace drivers

@ -52,8 +52,6 @@ class Display {
IGpios& gpio_; IGpios& gpio_;
spi_device_handle_t handle_; spi_device_handle_t handle_;
std::unique_ptr<tasks::Worker> worker_task_;
bool display_on_; bool display_on_;
uint_fast8_t brightness_; uint_fast8_t brightness_;

@ -19,44 +19,36 @@ auto Name() -> std::string;
template <> template <>
auto Name<Type::kUi>() -> std::string { auto Name<Type::kUi>() -> std::string {
return "LVGL"; return "ui";
} }
template <> template <>
auto Name<Type::kUiFlush>() -> std::string { auto Name<Type::kAudioDecoder>() -> std::string {
return "DISPLAY"; return "audio_dec";
} }
template <> template <>
auto Name<Type::kFileStreamer>() -> std::string { auto Name<Type::kAudioConverter>() -> std::string {
return "FSTREAMER"; return "audio_conv";
}
template <>
auto Name<Type::kAudio>() -> std::string {
return "AUDIO";
}
template <>
auto Name<Type::kMixer>() -> std::string {
return "MIXER";
} }
template <> template <>
auto Name<Type::kDatabase>() -> std::string { auto Name<Type::kDatabase>() -> std::string {
return "DB"; return "db_fg";
} }
template <> template <>
auto Name<Type::kDatabaseBackground>() -> std::string { auto Name<Type::kDatabaseBackground>() -> std::string {
return "DB_BG"; return "db_bg";
} }
template <> template <>
auto Name<Type::kNvsWriter>() -> std::string { auto Name<Type::kNvsWriter>() -> std::string {
return "NVS"; return "nvs";
} }
template <Type t> template <Type t>
auto AllocateStack() -> cpp::span<StackType_t>; auto AllocateStack() -> cpp::span<StackType_t>;
// Decoders run on the audio task, and these sometimes require a fairly large // Decoders often require a very large amount of stack space, since they aren't
// amount of stack space. // usually written with embedded use cases in mind.
template <> template <>
auto AllocateStack<Type::kAudio>() -> cpp::span<StackType_t> { auto AllocateStack<Type::kAudioDecoder>() -> cpp::span<StackType_t> {
std::size_t size = 64 * 1024; std::size_t size = 64 * 1024;
return {static_cast<StackType_t*>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM)), return {static_cast<StackType_t*>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM)),
size}; size};
@ -69,29 +61,15 @@ auto AllocateStack<Type::kUi>() -> cpp::span<StackType_t> {
return {static_cast<StackType_t*>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM)), return {static_cast<StackType_t*>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM)),
size}; 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 <> template <>
auto AllocateStack<Type::kUiFlush>() -> cpp::span<StackType_t> { // PCM conversion and resampling uses a very small amount of stack. It works
std::size_t size = 1024; // entirely with PSRAM-allocated buffers, so no real speed gain from allocating
return {static_cast<StackType_t*>(heap_caps_malloc(size, MALLOC_CAP_DEFAULT)), // it internally.
size}; auto AllocateStack<Type::kAudioConverter>() -> cpp::span<StackType_t> {
}
template <>
auto AllocateStack<Type::kFileStreamer>() -> cpp::span<StackType_t> {
std::size_t size = 4 * 1024; std::size_t size = 4 * 1024;
return {static_cast<StackType_t*>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM)), return {static_cast<StackType_t*>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM)),
size}; size};
} }
template <>
auto AllocateStack<Type::kMixer>() -> cpp::span<StackType_t> {
std::size_t size = 4 * 1024;
return {static_cast<StackType_t*>(heap_caps_malloc(size, MALLOC_CAP_SPIRAM)),
size};
}
// Leveldb is designed for non-embedded use cases, where stack space isn't so // 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. // much of a concern. It therefore uses an eye-wateringly large amount of stack.
template <> template <>
@ -114,8 +92,8 @@ auto AllocateStack<Type::kNvsWriter>() -> cpp::span<StackType_t> {
size}; size};
} }
// 2048 bytes in internal ram // 2 KiB in internal ram
// 302 KiB in external ram. // 612 KiB in external ram.
/* /*
* Please keep the priorities below in descending order for better readability. * Please keep the priorities below in descending order for better readability.
@ -124,39 +102,22 @@ auto AllocateStack<Type::kNvsWriter>() -> cpp::span<StackType_t> {
template <Type t> template <Type t>
auto Priority() -> UBaseType_t; auto Priority() -> UBaseType_t;
// NVS writing requires suspending one of our cores, and disabling tasks with // Realtime audio is the entire point of this device, so give these tasks the
// their stacks in PSRAM. Get it over and done with as soon as possible.
template <>
auto Priority<Type::kNvsWriter>() -> UBaseType_t {
return 13;
}
// Realtime audio is the entire point of this device, so give this task the
// highest priority. // highest priority.
template <> template <>
auto Priority<Type::kMixer>() -> UBaseType_t { auto Priority<Type::kAudioDecoder>() -> UBaseType_t {
return 12; return 18;
} }
template <> template <>
auto Priority<Type::kAudio>() -> UBaseType_t { auto Priority<Type::kAudioConverter>() -> UBaseType_t {
return 11; return 18;
} }
// After audio issues, UI jank is the most noticeable kind of scheduling-induced // 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 // 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 <> template <>
auto Priority<Type::kUi>() -> UBaseType_t { auto Priority<Type::kUi>() -> 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<Type::kUiFlush>() -> UBaseType_t {
return 9;
}
template <>
auto Priority<Type::kFileStreamer>() -> UBaseType_t {
return 10; return 10;
} }
// Database interactions are all inherently async already, due to their // Database interactions are all inherently async already, due to their
@ -165,11 +126,18 @@ auto Priority<Type::kFileStreamer>() -> UBaseType_t {
// priority. // priority.
template <> template <>
auto Priority<Type::kDatabase>() -> UBaseType_t { auto Priority<Type::kDatabase>() -> UBaseType_t {
return 8; return 2;
} }
template <> template <>
auto Priority<Type::kDatabaseBackground>() -> UBaseType_t { auto Priority<Type::kDatabaseBackground>() -> 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<Type::kNvsWriter>() -> UBaseType_t {
return 2;
} }
template <Type t> template <Type t>
@ -184,10 +152,6 @@ auto WorkerQueueSize<Type::kDatabaseBackground>() -> std::size_t {
return 8; return 8;
} }
template <>
auto WorkerQueueSize<Type::kUiFlush>() -> std::size_t {
return 2;
}
template <> template <>
auto WorkerQueueSize<Type::kNvsWriter>() -> std::size_t { auto WorkerQueueSize<Type::kNvsWriter>() -> std::size_t {
return 2; return 2;

@ -30,18 +30,17 @@ namespace tasks {
enum class Type { enum class Type {
// The main UI task. This runs the LVGL main loop. // The main UI task. This runs the LVGL main loop.
kUi, kUi,
// Task for flushing graphics buffers to the display. // The main audio pipeline task. Decodes files into PCM stream.
kUiFlush, kAudioDecoder,
// TODO. // Second audio task. Converts the PCM stream into one suitable for the
kFileStreamer, // current output (e.g. downsampling for bluetooth).
// The main audio pipeline task. kAudioConverter,
kAudio,
// TODO
kMixer,
// Task for running database queries. // Task for running database queries.
kDatabase, kDatabase,
// Task for internal database operations // Task for internal database operations
kDatabaseBackground, kDatabaseBackground,
// Task for interacting with NVS -- this needs to be done with an internal
// stack.
kNvsWriter, kNvsWriter,
}; };

@ -40,10 +40,6 @@ class UiTask {
std::shared_ptr<TouchWheelEncoder> input_device_; std::shared_ptr<TouchWheelEncoder> input_device_;
std::shared_ptr<Screen> current_screen_; std::shared_ptr<Screen> current_screen_;
std::atomic<bool> quit_;
SemaphoreHandle_t frame_semaphore_;
TimerHandle_t frame_timer_;
}; };
} // namespace ui } // namespace ui

@ -48,10 +48,9 @@ class UiState : public tinyfsm::Fsm<UiState> {
void react(const tinyfsm::Event& ev) {} void react(const tinyfsm::Event& ev) {}
virtual void react(const system_fsm::BatteryStateChanged&); 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::PlaybackUpdate&) {}
virtual void react(const audio::PlaybackFinished&) {}
virtual void react(const audio::QueueUpdate&) {} virtual void react(const audio::QueueUpdate&) {}
virtual void react(const system_fsm::KeyLockChanged&); virtual void react(const system_fsm::KeyLockChanged&);

@ -49,24 +49,8 @@
namespace ui { namespace ui {
static const char* kTag = "ui_task"; static const char* kTag = "ui_task";
static const TickType_t kMaxFrameRate = pdMS_TO_TICKS(100);
static auto next_frame(TimerHandle_t t) { UiTask::UiTask() {}
SemaphoreHandle_t sem =
reinterpret_cast<SemaphoreHandle_t>(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); assert(false);
@ -98,10 +82,8 @@ auto UiTask::Main() -> void {
current_screen_->Tick(); current_screen_->Tick();
} }
lv_task_handler(); TickType_t delay = lv_timer_handler();
vTaskDelay(pdMS_TO_TICKS(std::clamp<TickType_t>(delay, 0, 100)));
// Wait for the signal to loop again.
xSemaphoreTake(frame_semaphore_, portMAX_DELAY);
} }
} }
@ -114,7 +96,7 @@ auto UiTask::SetInputDevice(std::shared_ptr<TouchWheelEncoder> dev) -> void {
auto UiTask::Start() -> UiTask* { auto UiTask::Start() -> UiTask* {
UiTask* ret = new UiTask(); UiTask* ret = new UiTask();
tasks::StartPersistent<tasks::Type::kUi>(0, [=]() { ret->Main(); }); tasks::StartPersistent<tasks::Type::kUi>([=]() { ret->Main(); });
return ret; return ret;
} }

@ -90,6 +90,13 @@ void UiState::react(const system_fsm::BatteryStateChanged&) {
UpdateTopBar(); UpdateTopBar();
} }
void UiState::react(const audio::PlaybackStarted&) {
vTaskPrioritySet(NULL, 0);
}
void UiState::react(const audio::PlaybackFinished&) {
vTaskPrioritySet(NULL, 10);
}
void UiState::UpdateTopBar() { void UiState::UpdateTopBar() {
auto battery_state = sServices->battery().State(); auto battery_state = sServices->battery().State();
bool has_queue = sServices->track_queue().GetCurrent().has_value(); bool has_queue = sServices->track_queue().GetCurrent().has_value();
@ -251,6 +258,7 @@ void Playing::exit() {
} }
void Playing::react(const audio::PlaybackStarted& ev) { void Playing::react(const audio::PlaybackStarted& ev) {
vTaskPrioritySet(NULL, 0);
UpdateTopBar(); UpdateTopBar();
sPlayingScreen->OnTrackUpdate(); sPlayingScreen->OnTrackUpdate();
} }
@ -261,6 +269,7 @@ void Playing::react(const audio::PlaybackUpdate& ev) {
void Playing::react(const audio::PlaybackFinished& ev) { void Playing::react(const audio::PlaybackFinished& ev) {
UpdateTopBar(); UpdateTopBar();
vTaskPrioritySet(NULL, 10);
} }
void Playing::react(const audio::QueueUpdate& ev) { void Playing::react(const audio::QueueUpdate& ev) {

Loading…
Cancel
Save