diff --git a/README.md b/README.md index 0705921..1d2e054 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,10 @@ your Wi-Fi. Holding the knob down while the widget starts forces the portal. bodies without `Content-Length`, which is exactly what MJPEG cameras send. * `jpegframe.c` pulls complete JPEG files out of the byte stream by walking the marker structure (so an EXIF thumbnail's EOI can't truncate a frame). -* `jpegdec.c` decodes with `esp_jpeg` at the largest power-of-two reduction - that still covers the screen, then bilinear-resamples to a full-bleed 320×170. +* `jpegdec.c` decodes with Espressif's `esp_new_jpeg`, scaling inside the + decoder to the smallest multiple-of-8 size that still covers the screen, then + bilinear-resamples the remainder to a full-bleed 320×170 (≈100 ms for a + 1280×800 frame, so MJPEG streams play at a few fps). * `slideshow.c` runs the state machine: connect, play frames until the interval elapses or the knob moves, skip dead cameras, keep history for "previous". * `ui.c` is LVGL 9 via `esp_lvgl_port`: a canvas for the frame, a translucent diff --git a/dependencies.lock b/dependencies.lock index 967fa13..4c19ba5 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -23,16 +23,6 @@ dependencies: registry_url: https://components.espressif.com type: service version: 0.5.3 - espressif/esp_jpeg: - component_hash: defb83669293cbf86d0fa86b475ba5517aceed04ed70db435388c151ab37b5d7 - dependencies: - - name: idf - require: private - version: '>=5.0' - source: - registry_url: https://components.espressif.com/ - type: service - version: 1.3.1 espressif/esp_lvgl_port: component_hash: d3c020b45c3dfc0d1706a83b63a1c34a6eb444a6e935f9afff79f92b7b94dcef dependencies: @@ -47,6 +37,25 @@ dependencies: registry_url: https://components.espressif.com/ type: service version: 2.9.0 + espressif/esp_new_jpeg: + component_hash: e1e7e6991193c41ed6c9fc386702aa7821433319e7aa9c3147cd3236310cd69c + dependencies: [] + source: + registry_url: https://components.espressif.com/ + type: service + targets: + - esp32 + - esp32s2 + - esp32s3 + - esp32s31 + - esp32p4 + - esp32c2 + - esp32c3 + - esp32c5 + - esp32c6 + - esp32c61 + - esp32h4 + version: 1.0.2 espressif/knob: component_hash: b56694054cd34a683ac2a6f3661963ba00b1dab741a4f3bb59ed6666cbc9f3fb dependencies: @@ -108,12 +117,12 @@ dependencies: version: 9.6.0 direct_dependencies: - espressif/button -- espressif/esp_jpeg - espressif/esp_lvgl_port +- espressif/esp_new_jpeg - espressif/knob - espressif/led_strip - idf - lvgl/lvgl -manifest_hash: d33b0997c1d650a5851082c8eb06024622fb751cdfbef43544acf0e98406433c +manifest_hash: c3ae35dba19eb151e16226d84853ce33ee1ef723b08192955e54805732cd49fd target: esp32s3 version: 2.0.0 diff --git a/main/idf_component.yml b/main/idf_component.yml index 80e3816..df85755 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -2,7 +2,7 @@ dependencies: idf: ">=5.5,<6" lvgl/lvgl: "^9.3" espressif/esp_lvgl_port: "^2.7" - espressif/esp_jpeg: "^1.3" espressif/knob: "^1.1" espressif/button: "^4.2" espressif/led_strip: "^3.0" + espressif/esp_new_jpeg: "^1.0" diff --git a/main/insecam.c b/main/insecam.c index aa5bfa7..113cc3f 100644 --- a/main/insecam.c +++ b/main/insecam.c @@ -106,6 +106,7 @@ static int parse_page(char *html, cam_info_t *out, int max, int *total_pages) item = next; continue; } + for (int pass = 0; pass < 3 && strstr(c->url, "&"); pass++) clean_text(c->url, sizeof c->url); clean_text(c->url, sizeof c->url); fill_counter(c->url); // ; fall back to the link title diff --git a/main/jpegdec.c b/main/jpegdec.c index 5517091..191083b 100644 --- a/main/jpegdec.c +++ b/main/jpegdec.c @@ -1,24 +1,21 @@ #include "jpegdec.h" #include "display.h" -#include "jpeg_decoder.h" +#include "esp_jpeg_dec.h" +#include "esp_timer.h" #include "esp_heap_caps.h" #include "esp_log.h" #include static const char *TAG = "jpeg"; -#define WORK_BUF_SIZE 65472 // tjpgd scratch with JD_FASTDECODE=2 #define MAX_INTERMEDIATE (2 * 1024 * 1024) -static void *work_buf; static uint16_t *mid; // decoded image before resampling (PSRAM) static size_t mid_cap; esp_err_t jpegdec_init(void) { - work_buf = heap_caps_malloc(WORK_BUF_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); - if (!work_buf) return ESP_ERR_NO_MEM; mid_cap = 640 * 360 * 2; - mid = heap_caps_malloc(mid_cap, MALLOC_CAP_SPIRAM); + mid = heap_caps_aligned_alloc(16, mid_cap, MALLOC_CAP_SPIRAM); // esp_new_jpeg wants 16-byte alignment return mid ? ESP_OK : ESP_ERR_NO_MEM; } @@ -77,6 +74,59 @@ static void resample_cover(const uint16_t *src, int sw, int sh, uint16_t *dst) } } +static int64_t ms(void) { return esp_timer_get_time() / 1000; } + +// esp_new_jpeg path: scale in the decoder to the smallest multiple-of-8 size that still +// covers the screen (uniform factor, min 1/8), then bilinear-resample the last few percent. +static esp_err_t decode_new(const uint8_t *jpeg, size_t len, int w, int h, uint16_t *dst, int64_t *t_dec) +{ + jpeg_dec_config_t cfg = DEFAULT_JPEG_DEC_CONFIG(); + cfg.output_type = JPEG_PIXEL_FORMAT_RGB565_LE; + float f = LCD_H_RES / (float)w; + if (LCD_V_RES / (float)h > f) f = LCD_V_RES / (float)h; + if (f < 0.125f) f = 0.125f; + int sw = w, sh = h; + if (f < 1.0f) { + sw = ((int)(w * f + 7) / 8) * 8; + sh = ((int)(h * f + 7) / 8) * 8; + if (sw < 8) sw = 8; + if (sh < 8) sh = 8; + cfg.scale.width = sw; + cfg.scale.height = sh; + } + size_t need = (size_t)(sw + 16) * (sh + 16) * 2; + if (need > MAX_INTERMEDIATE) return ESP_ERR_INVALID_SIZE; + if (need > mid_cap) { + uint16_t *bigger = heap_caps_aligned_alloc(16, need, MALLOC_CAP_SPIRAM); + if (!bigger) return ESP_ERR_NO_MEM; + heap_caps_free(mid); + mid = bigger; + mid_cap = need; + } + jpeg_dec_handle_t dec = NULL; + if (jpeg_dec_open(&cfg, &dec) != JPEG_ERR_OK) return ESP_FAIL; + jpeg_dec_io_t io = {.inbuf = (uint8_t *)jpeg, .inbuf_len = len}; + jpeg_dec_header_info_t info = {0}; + jpeg_error_t err = jpeg_dec_parse_header(dec, &io, &info); + int outlen = 0; + if (err == JPEG_ERR_OK) err = jpeg_dec_get_outbuf_len(dec, &outlen); + if (err == JPEG_ERR_OK && (size_t)outlen > mid_cap) err = JPEG_ERR_NO_MEM; + int64_t t0 = ms(); + if (err == JPEG_ERR_OK) { + io.outbuf = (uint8_t *)mid; + err = jpeg_dec_process(dec, &io); + } + *t_dec = ms() - t0; + jpeg_dec_close(dec); + if (err != JPEG_ERR_OK) { + ESP_LOGW(TAG, "esp_new_jpeg failed (%d) %dx%d -> %dx%d", err, w, h, sw, sh); + return ESP_FAIL; + } + int ow = cfg.scale.width ? sw : w, oh = cfg.scale.height ? sh : h; + resample_cover(mid, ow, oh, dst); + return ESP_OK; +} + esp_err_t jpegdec_to_screen(const uint8_t *jpeg, size_t len, uint16_t *dst, int *src_w, int *src_h) { int w, h; @@ -84,34 +134,8 @@ esp_err_t jpegdec_to_screen(const uint8_t *jpeg, size_t len, uint16_t *dst, int if (err != ESP_OK) return err; *src_w = w; *src_h = h; - - // Largest power-of-two reduction that still covers the screen. - int scale = 0; - while (scale < 3 && (w >> (scale + 1)) >= LCD_H_RES && (h >> (scale + 1)) >= LCD_V_RES) scale++; - int sw = w >> scale, sh = h >> scale; - // tjpgd rounds the scaled size up to whole MCUs; give it room. - size_t need = (size_t)(sw + 16) * (sh + 16) * 2; - if (need > MAX_INTERMEDIATE) return ESP_ERR_INVALID_SIZE; - if (need > mid_cap) { - uint16_t *bigger = heap_caps_realloc(mid, need, MALLOC_CAP_SPIRAM); - if (!bigger) return ESP_ERR_NO_MEM; - mid = bigger; - mid_cap = need; - } - - esp_jpeg_image_cfg_t cfg = { - .indata = (uint8_t *)jpeg, .indata_size = len, - .outbuf = (uint8_t *)mid, .outbuf_size = mid_cap, - .out_format = JPEG_IMAGE_FORMAT_RGB565, .out_scale = (esp_jpeg_image_scale_t)scale, - .flags.swap_color_bytes = 0, - .advanced = {.working_buffer = work_buf, .working_buffer_size = WORK_BUF_SIZE}, - }; - esp_jpeg_image_output_t out = {0}; - err = esp_jpeg_decode(&cfg, &out); - if (err != ESP_OK) { - ESP_LOGW(TAG, "decode failed (%s) %dx%d scale 1/%d", esp_err_to_name(err), w, h, 1 << scale); - return err; - } - resample_cover(mid, out.width, out.height, dst); - return ESP_OK; + int64_t t_dec = 0, t0 = ms(); + err = decode_new(jpeg, len, w, h, dst, &t_dec); + ESP_LOGD(TAG, "%dx%d %u bytes: decode %lld ms, total %lld ms", w, h, (unsigned)len, (long long)t_dec, (long long)(ms() - t0)); + return err; } diff --git a/sdkconfig.defaults b/sdkconfig.defaults index d70c96d..063c237 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -48,7 +48,3 @@ CONFIG_LV_USE_BAR=y CONFIG_LV_USE_LABEL=y CONFIG_LV_USE_PERF_MONITOR=n -# esp_jpeg: the ROM tjpgd is slow; use the bundled one with table-driven Huffman decoding -CONFIG_JD_USE_ROM=n -CONFIG_JD_FASTDECODE_TABLE=y -CONFIG_JD_FORMAT_RGB565=y