-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Add support for waveshare s3 touch hardware and use framebuffers to allow decode failures to be discarded without visual artifacts
#13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -30,28 +30,47 @@ using hal = espp::TDeck; | |||||||||||||||||||||||
| #elif CONFIG_HARDWARE_BYTE90 | ||||||||||||||||||||||||
| #include "byte90.hpp" | ||||||||||||||||||||||||
| using hal = espp::Byte90; | ||||||||||||||||||||||||
| #elif CONFIG_HARDWARE_WS_S3_TOUCH | ||||||||||||||||||||||||
| #include "ws-s3-touch.hpp" | ||||||||||||||||||||||||
| using hal = espp::WsS3Touch; | ||||||||||||||||||||||||
| #else | ||||||||||||||||||||||||
| #error "No hardware defined" | ||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| using namespace std::chrono_literals; | ||||||||||||||||||||||||
| using DisplayDriver = hal::DisplayDriver; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| static espp::Logger logger({.tag = "Camera Display", .level = espp::Logger::Verbosity::INFO}); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // frame buffers for decoding into | ||||||||||||||||||||||||
| static uint8_t *fb0 = nullptr; | ||||||||||||||||||||||||
| static uint8_t *fb1 = nullptr; | ||||||||||||||||||||||||
| // DRAM for actual vram (used by SPI to send to LCD) | ||||||||||||||||||||||||
| static uint8_t *vram0 = nullptr; | ||||||||||||||||||||||||
| static uint8_t *vram1 = nullptr; | ||||||||||||||||||||||||
| static std::atomic<int> num_frames_received{0}; | ||||||||||||||||||||||||
| static std::atomic<int> num_frames_displayed{0}; | ||||||||||||||||||||||||
| static std::atomic<float> elapsed{0}; | ||||||||||||||||||||||||
| static std::chrono::high_resolution_clock::time_point connected_time; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // video | ||||||||||||||||||||||||
| static std::unique_ptr<espp::Task> video_task_{nullptr}; | ||||||||||||||||||||||||
| static QueueHandle_t video_queue_{nullptr}; | ||||||||||||||||||||||||
| static bool initialize_video(); | ||||||||||||||||||||||||
| static bool video_task_callback(std::mutex &m, std::condition_variable &cv, bool &task_notified); | ||||||||||||||||||||||||
| static void clear_screen(); | ||||||||||||||||||||||||
| static void push_frame(const void *frame); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // rtsp | ||||||||||||||||||||||||
| std::unique_ptr<espp::Task> start_rtsp_task; | ||||||||||||||||||||||||
| static std::shared_ptr<espp::RtspClient> rtsp_client; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| static constexpr size_t vram_size = hal::lcd_width() * 50 * 2; // 50 lines of 16-bit pixels | ||||||||||||||||||||||||
| static constexpr int num_rows_in_vram = 50; | ||||||||||||||||||||||||
| static constexpr size_t vram_size = hal::lcd_width() * num_rows_in_vram * sizeof(hal::Pixel); | ||||||||||||||||||||||||
| static constexpr size_t fb_size = hal::lcd_width() * hal::lcd_height() * sizeof(hal::Pixel); | ||||||||||||||||||||||||
| static std::mutex jpeg_mutex; | ||||||||||||||||||||||||
| static std::condition_variable jpeg_cv; | ||||||||||||||||||||||||
| static constexpr size_t MAX_JPEG_FRAMES = 2; | ||||||||||||||||||||||||
| static constexpr size_t MAX_JPEG_FRAMES = 3; | ||||||||||||||||||||||||
| static std::deque<std::shared_ptr<espp::JpegFrame>> jpeg_frames; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| bool start_rtsp_client(std::mutex &m, std::condition_variable &cv, bool &task_notified); | ||||||||||||||||||||||||
|
|
@@ -71,6 +90,21 @@ extern "C" void app_main(void) { | |||||||||||||||||||||||
| return; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // allocate some frame buffers for jpeg decoding, which should be screen-size | ||||||||||||||||||||||||
| // and in PSRAM | ||||||||||||||||||||||||
| fb0 = (uint8_t *)heap_caps_malloc(fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); | ||||||||||||||||||||||||
| fb1 = (uint8_t *)heap_caps_malloc(fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); | ||||||||||||||||||||||||
| if (!fb0 || !fb1) { | ||||||||||||||||||||||||
| logger.error("Could not allocate frame buffers for LCD"); | ||||||||||||||||||||||||
| if (fb0) { | ||||||||||||||||||||||||
| heap_caps_free(fb0); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if (fb1) { | ||||||||||||||||||||||||
| heap_caps_free(fb1); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // allocate some DMA-capable VRAM for jpeg decoding / display operations | ||||||||||||||||||||||||
| vram0 = (uint8_t *)heap_caps_malloc(vram_size, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); | ||||||||||||||||||||||||
| vram1 = (uint8_t *)heap_caps_malloc(vram_size, MALLOC_CAP_DMA | MALLOC_CAP_8BIT); | ||||||||||||||||||||||||
|
|
@@ -85,6 +119,19 @@ extern "C" void app_main(void) { | |||||||||||||||||||||||
| return; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| logger.info("Allocated frame buffers: fb0 = {} B, fb1 = {} B", fb_size, fb_size); | ||||||||||||||||||||||||
| logger.info("Allocated VRAM: vram0 = {} B, vram1 = {} B", vram_size, vram_size); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // initialize the video task | ||||||||||||||||||||||||
| if (!initialize_video()) { | ||||||||||||||||||||||||
| logger.error("Could not initialize video task"); | ||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // clear the screen | ||||||||||||||||||||||||
| logger.info("Clearing screen"); | ||||||||||||||||||||||||
| clear_screen(); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // create the parsing and display task | ||||||||||||||||||||||||
| logger.info("Starting display task"); | ||||||||||||||||||||||||
| auto display_task = espp::Task::make_unique({.callback = display_task_fn, | ||||||||||||||||||||||||
|
|
@@ -107,6 +154,7 @@ extern "C" void app_main(void) { | |||||||||||||||||||||||
| espp::WifiSta wifi_sta( | ||||||||||||||||||||||||
| {.ssid = CONFIG_ESP_WIFI_SSID, | ||||||||||||||||||||||||
| .password = CONFIG_ESP_WIFI_PASSWORD, | ||||||||||||||||||||||||
| // .phy_rate = WIFI_PHY_RATE_MCS5_SGI, | ||||||||||||||||||||||||
| .num_connect_retries = CONFIG_ESP_MAXIMUM_RETRY, | ||||||||||||||||||||||||
| .on_connected = []() { logger.info("Connected to WiFi, waiting for IP address"); }, | ||||||||||||||||||||||||
| .on_disconnected = | ||||||||||||||||||||||||
|
|
@@ -292,6 +340,7 @@ bool start_rtsp_client(std::mutex &m, std::condition_variable &cv, bool &task_no | |||||||||||||||||||||||
| return true; // we're done with our work, no need to run again, so stop the task | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| static size_t frame_buffer_index = 0; | ||||||||||||||||||||||||
| // function for drawing the minimum compressible units | ||||||||||||||||||||||||
| // cppcheck-suppress constParameterCallback | ||||||||||||||||||||||||
| int drawMCUs(JPEGDRAW *pDraw) { | ||||||||||||||||||||||||
|
|
@@ -301,17 +350,16 @@ int drawMCUs(JPEGDRAW *pDraw) { | |||||||||||||||||||||||
| auto xe = pDraw->x + pDraw->iWidth - 1; | ||||||||||||||||||||||||
| auto ye = pDraw->y + pDraw->iHeight - 1; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (iCount * 2 > vram_size) { | ||||||||||||||||||||||||
| logger.error("Not enough VRAM for image: {} B, available: {} B", iCount * 2, vram_size); | ||||||||||||||||||||||||
| return 0; // not enough VRAM to draw the image | ||||||||||||||||||||||||
| uint16_t *dst = (uint16_t *)(frame_buffer_index ? fb1 : fb0); | ||||||||||||||||||||||||
| uint16_t *src = (uint16_t *)(pDraw->pPixels); | ||||||||||||||||||||||||
| // copy the pixels from the JPEG draw structure to the framebuffer at the | ||||||||||||||||||||||||
| // appropriate position | ||||||||||||||||||||||||
| for (int row = 0; row < pDraw->iHeight; row++) { | ||||||||||||||||||||||||
| // copy a whole row at a time | ||||||||||||||||||||||||
| memcpy(&dst[(ys + row) * hal::lcd_width() + xs], &src[row * pDraw->iWidth], | ||||||||||||||||||||||||
| pDraw->iWidth * sizeof(uint16_t)); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| static size_t frame_buffer_index = 0; | ||||||||||||||||||||||||
| uint8_t *out_img_buf = (uint8_t *)(frame_buffer_index ? vram1 : vram0); | ||||||||||||||||||||||||
| frame_buffer_index = frame_buffer_index ? 0 : 1; | ||||||||||||||||||||||||
| memcpy(out_img_buf, pDraw->pPixels, iCount * 2); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| hal::get().write_lcd_lines(xs, ys, xe, ye, out_img_buf, 0); | ||||||||||||||||||||||||
| // returning true (1) tells JPEGDEC to continue decoding. Returning false | ||||||||||||||||||||||||
| // (0) would quit decoding immediately. | ||||||||||||||||||||||||
| return 1; | ||||||||||||||||||||||||
|
|
@@ -335,19 +383,25 @@ bool display_task_fn(std::mutex &m, std::condition_variable &cv) { | |||||||||||||||||||||||
| auto image_data = image->get_data(); | ||||||||||||||||||||||||
| logger.info("Decoding image of size {} B, shape = {} x {}", image_data.size(), image->get_width(), | ||||||||||||||||||||||||
| image->get_height()); | ||||||||||||||||||||||||
| // update to the current frame buffer index | ||||||||||||||||||||||||
| frame_buffer_index = frame_buffer_index ^ 0x01; | ||||||||||||||||||||||||
| if (jpeg.openRAM((uint8_t *)(image_data.data()), image_data.size(), drawMCUs)) { | ||||||||||||||||||||||||
| logger.debug("Image size: {} x {}, orientation: {}, bpp: {}", jpeg.getWidth(), jpeg.getHeight(), | ||||||||||||||||||||||||
| jpeg.getOrientation(), jpeg.getBpp()); | ||||||||||||||||||||||||
| jpeg.setPixelType(RGB565_BIG_ENDIAN); | ||||||||||||||||||||||||
| if (!jpeg.decode(0, 0, 0)) { | ||||||||||||||||||||||||
| logger.error("Error decoding"); | ||||||||||||||||||||||||
| // decode the JPEG image | ||||||||||||||||||||||||
| if (!jpeg.decode(0, 0, JPEG_USES_DMA)) { | ||||||||||||||||||||||||
| logger.debug("Error decoding"); | ||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||
| num_frames_displayed += 1; | ||||||||||||||||||||||||
| // push the frame for rendering | ||||||||||||||||||||||||
| push_frame(frame_buffer_index ? fb1 : fb0); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||
| logger.error("error opening jpeg image"); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| auto end = std::chrono::high_resolution_clock::now(); | ||||||||||||||||||||||||
| elapsed = std::chrono::duration<float>(end - start).count(); | ||||||||||||||||||||||||
| num_frames_displayed += 1; | ||||||||||||||||||||||||
| // signal that we do not want to stop the task | ||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
@@ -440,3 +494,83 @@ bool find_mdns_service(const char *service_name, const char *proto, std::string | |||||||||||||||||||||||
| mdns_query_results_free(results); | ||||||||||||||||||||||||
| return found_service; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| /// Video related functions: | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| bool initialize_video() { | ||||||||||||||||||||||||
| if (video_queue_ || video_task_) { | ||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| video_queue_ = xQueueCreate(1, sizeof(uint16_t *)); | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| video_queue_ = xQueueCreate(1, sizeof(uint16_t *)); | |
| video_queue_ = xQueueCreate(1, sizeof(void *)); |
Copilot
AI
Aug 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function passes the address of the local parameter frame to the queue instead of the frame pointer value itself. This should be xQueueSend(video_queue_, &frame, portMAX_DELAY) where frame is already a pointer, or the queue should be defined to hold void* directly rather than uint16_t*.
Copilot
AI
Aug 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The buffer selection logic (uint32_t)vram0 * (vram_index ^ 0x01) + (uint32_t)vram1 * vram_index is duplicated and complex. Extract this into a helper function or use a simpler ternary operator like vram_index ? vram1 : vram0 for better readability and maintainability.
| Pixel *_buf = (Pixel *)((uint32_t)vram0 * (vram_index ^ 0x01) + (uint32_t)vram1 * vram_index); | |
| Pixel *_buf = (Pixel *)(vram_index ? vram1 : vram0); |
Copilot
AI
Aug 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The loop assumes lcd_width is always even, but if it's odd, this will access memory beyond the allocated buffer bounds on the last iteration. Add a check to handle odd widths or ensure the loop doesn't exceed the buffer size.
| } | |
| int j = 0; | |
| for (; j + 1 < lcd_width; j += 2) { | |
| uint32_t *src = (uint32_t *)&_frame[(y + i) * lcd_width + j]; | |
| uint32_t *dst = (uint32_t *)&_buf[i * lcd_width + j]; | |
| dst[0] = src[0]; // copy two pixels (32 bits) at a time | |
| } | |
| // If lcd_width is odd, copy the last pixel | |
| if (lcd_width % 2 != 0) { | |
| _buf[i * lcd_width + lcd_width - 1] = _frame[(y + i) * lcd_width + lcd_width - 1]; | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The static variable
frame_buffer_indexis accessed from multiple tasks (display task and video task) without synchronization. This creates a race condition that could lead to corruption or incorrect buffer selection. Consider using atomic operations or protecting access with a mutex.