Decode with esp_new_jpeg; fully decode double-encoded camera URLs
esp_new_jpeg scales inside the decoder and is 5-6x faster than tjpgd on the S3 (1280x800 frame: ~100 ms instead of ~600 ms), so MJPEG streams now play at a few fps. insecam double-encodes some URLs (&); the entity decoder now loops until stable.
This commit is contained in:
1 parent
65a391940b
commit
eff9e5dfaa
6 files changed
+87
-55
No files matched your search
@@ -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
|
||||
|
||||
+21
-12
@@ -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
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
// <img ... title="Live camera Mobotix in Imola, Italy"> ; fall back to the link title
|
||||
|
||||
+60
-36
@@ -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 <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in new issue
Block a user