src.nth.io/

summaryrefslogtreecommitdiff
path: root/main/stream_server.h
diff options
context:
space:
mode:
authorLuke Hoersten <[email protected]>2026-06-20 20:50:43 -0500
committerLuke Hoersten <[email protected]>2026-06-20 20:50:43 -0500
commitd1c8d45d5dc8ae09f03e2f0a9c6b3ac1910b8cdc (patch)
tree1a62dc06bbeda01d536ce1bd19204bae5c055933 /main/stream_server.h
parent5b3b3d3f74fffdcf4023d3f0f3fe9729fadfb8e2 (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/stream_server.h')
-rw-r--r--main/stream_server.h8
1 files changed, 8 insertions, 0 deletions
diff --git a/main/stream_server.h b/main/stream_server.h
index 5de1e1f..e2eba06 100644
--- a/main/stream_server.h
+++ b/main/stream_server.h
@@ -62,6 +62,14 @@ typedef struct {
uint32_t recv_calls_min, recv_calls_avg, recv_calls_max; // syscalls per frame body
uint32_t recv_chunk_min, recv_chunk_avg, recv_chunk_max; // bytes per recv() return
uint32_t so_rcvbuf; // SO_RCVBUF observed at accept (0 = unknown)
+ // Receiver-side skip-oldest. Counts frames where recv-task finished
+ // a body but the previous frame was still sitting in the pending
+ // slot (decode-task hadn't taken it yet). The older pending frame
+ // gets overwritten by the just-received fresher one; the counter
+ // captures how often the recv loop outran decode in this window.
+ uint32_t recv_dropped_oldest;
+ uint32_t decode_idle_min_us, decode_idle_avg_us, decode_idle_max_us;
+ // Time decode-task spent waiting on the slot signal between frames.
uint32_t last_paint_event_us_low; // last v1 frame's event_us_low,
// 0 if none seen yet on this
// boot or last frame was v0