diff options
| author | Luke Hoersten <[email protected]> | 2026-06-20 20:50:43 -0500 |
|---|---|---|
| committer | Luke Hoersten <[email protected]> | 2026-06-20 20:50:43 -0500 |
| commit | d1c8d45d5dc8ae09f03e2f0a9c6b3ac1910b8cdc (patch) | |
| tree | 1a62dc06bbeda01d536ce1bd19204bae5c055933 /main/jpeg_decoder.c | |
| parent | 5b3b3d3f74fffdcf4023d3f0f3fe9729fadfb8e2 (diff) | |
firmware: split stream recv into its own task with 3-buffer ping-pong
handle_client previously ran recv → decode → paint serially on one
FreeRTOS task. The kernel TCP buffer filled during decode+paint
(~6ms), and against the IDF-default 5760-byte window the sender
naturally stop-go-rate-limited to ~consumption. Raising the window
to 65535 (previous experiment) regressed g2g from ~100ms to 17s
growing unbounded — the sender pumped 45+ segments per round into
a kernel buffer the app couldn't drain in time, and there was no
way to skip-oldest on the kernel queue.
This commit decouples recv from decode+paint:
recv-task: owns the socket. Reads header + body into one of three
preallocated PSRAM body buffers. On body complete, swaps
the just-filled buffer into a 1-deep pending slot and
picks a free buffer for the next recv. If the slot
already held a frame (decode is slow), drops oldest in
place — mirror of the Scrypted-side skip-oldest from
e5acf93.
decode-task: waits on a binary semaphore. On signal, claims pending,
then decodes + paints without holding any shared lock.
Frees its prior buffer implicitly by overwriting
s_decode_idx on the next claim.
3 PSRAM body buffers (~3MB of 28MB free) ensure the invariant
{recv_idx, pending_idx, decode_idx} are pairwise distinct without
ever blocking recv. jpeg_decoder.c grew an alloc_input_buffer helper
+ jpeg_decoder_decode now takes an explicit input pointer so the
stream and http_api snapshot paths don't share scratch.
New stats:
- recv_dropped_oldest: per-window count of pending-slot overwrites
- decode_idle_min/avg/max_us: time decode-task spent waiting on signal
Measurement at IDF-default 5760 window, Unifi medium substream:
before split: recv_avg=32ms recv_max~44ms fps=22-26 (recv blocked
during 6ms decode+paint; chunk_max capped at 5760)
after split: recv_avg=17ms recv_max=18-37ms fps=21-29 steady,
decode_idle_avg=27-40ms (decode mostly waiting),
drop_oldest=0, painted at source rate
The bottleneck moved from 'decode+paint serializes recv' to the
wire's own send rate. Bigger windows are now safe (recv-task drains
continuously, can't bury us), but won't add fps until source rate
goes up — that's a separate conversation.
Diffstat (limited to 'main/jpeg_decoder.c')
| -rw-r--r-- | main/jpeg_decoder.c | 24 |
1 files changed, 18 insertions, 6 deletions
diff --git a/main/jpeg_decoder.c b/main/jpeg_decoder.c index cbb98d8..7fee771 100644 --- a/main/jpeg_decoder.c +++ b/main/jpeg_decoder.c @@ -54,14 +54,26 @@ void jpeg_decoder_unlock(void) void *jpeg_decoder_input_buffer(void) { return s_in_buf; } -esp_err_t jpeg_decoder_decode(size_t jpeg_len, +void *jpeg_decoder_alloc_input_buffer(size_t *out_cap) +{ + jpeg_decode_memory_alloc_cfg_t in_cfg = { .buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER }; + size_t cap = 0; + void *buf = jpeg_alloc_decoder_mem(JPEG_DECODER_MAX_INPUT_BYTES, &in_cfg, &cap); + if (out_cap) *out_cap = buf ? cap : 0; + return buf; +} + +esp_err_t jpeg_decoder_decode(void *in_buf, + size_t jpeg_len, void *out_buf, size_t out_cap, uint16_t *out_width, uint16_t *out_height) { - if (jpeg_len == 0 || jpeg_len > s_in_cap) return ESP_ERR_INVALID_SIZE; - if (!out_buf || out_cap == 0) return ESP_ERR_INVALID_ARG; + if (!in_buf) return ESP_ERR_INVALID_ARG; + if (jpeg_len == 0 || + jpeg_len > JPEG_DECODER_MAX_INPUT_BYTES) return ESP_ERR_INVALID_SIZE; + if (!out_buf || out_cap == 0) return ESP_ERR_INVALID_ARG; // Hardware decode directly into the caller's BGR888 buffer (the // panel back-framebuffer in the /frame path). _BGR rgb_order swaps @@ -75,7 +87,7 @@ esp_err_t jpeg_decoder_decode(size_t jpeg_len, }; jpeg_decode_picture_info_t info = {0}; - esp_err_t err = jpeg_decoder_get_info(s_in_buf, jpeg_len, &info); + esp_err_t err = jpeg_decoder_get_info(in_buf, jpeg_len, &info); if (err != ESP_OK) { ESP_LOGW(TAG, "jpeg_decoder_get_info: %s", esp_err_to_name(err)); return err; @@ -83,8 +95,8 @@ esp_err_t jpeg_decoder_decode(size_t jpeg_len, uint32_t out_size = 0; err = jpeg_decoder_process(s_engine, &dec_cfg, - s_in_buf, jpeg_len, - out_buf, out_cap, &out_size); + in_buf, jpeg_len, + out_buf, out_cap, &out_size); if (err != ESP_OK) { ESP_LOGW(TAG, "jpeg_decoder_process: %s", esp_err_to_name(err)); return err; |
