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