diff options
| author | Luke Hoersten <[email protected]> | 2026-06-19 19:59:14 -0500 |
|---|---|---|
| committer | Luke Hoersten <[email protected]> | 2026-06-19 19:59:14 -0500 |
| commit | b97c2502b74277f9ea9dace97e8188201d5570fb (patch) | |
| tree | 5a36c4f3f06ae3e310da5d42e3df84e989e91e3b /main/app_main.c | |
| parent | c89f832eb2c10f007ecf000165625c9953216a82 (diff) | |
streaming pivot: raw TCP data plane, drop per-frame HTTP entirely
Replaces the per-frame HTTP POST loop with a single long-lived TCP
connection on port 81. The HTTP control plane (/state, /config,
/frame for snapshot) stays unchanged.
Wire protocol (big-endian, repeating until connection close):
[4 bytes jpeg_len][4 bytes seq][jpeg_len bytes JPEG]
Why
---
The HTTP path hit a measured floor of ~37ms p50 with intermittent
~230ms p95 spikes that survived every Nagle/keep-alive fix attempt.
Each frame paid: TCP setup (or pool churn), HTTP parsing, body recv,
decode, paint, response write, response ACK. Streaming removes
everything except recv + decode + paint.
Firmware (main/stream_server.[ch], new)
---------------------------------------
- TCP listen on configurable port (81) in its own FreeRTOS task,
one client at a time (matches the one-stream-per-device model).
- Per accepted socket: TCP_NODELAY on, then loop reading 8-byte
header → jpeg body → through the existing jpeg_decoder + display
paths. Same stale-seq guard as the HTTP /frame handler (reset per
connection so each session starts at seq 1).
- Frames received while asleep are still drained (to stay framed)
but not painted. The HTTP control-plane POST /state {wake}
resumes painting on the next frame.
- Every 30 painted frames a structured serial log shows per-stage
timing + sustained MB/s — replaces the cross-side Server-Timing
header (no HTTP response to attach it to anymore).
Scrypted side
-------------
- net.createConnection({ host, port: 81, noDelay: true }) opened
once per stream session. On disconnect/error/close we auto-
reconnect after 500ms.
- ffmpeg stdout demux writes [header][body] directly to the
socket. sock.write() returning false sets a backpressured flag
that drops incoming ffmpeg frames until 'drain' fires — natural
TCP backpressure handles "firmware can't keep up" without us
modeling it manually.
- Stripped the entire fetch-based timing infrastructure
(pushStreamFrame, fetchSamples, parseServerTiming, depth
histogram, Server-Timing parser). Replaced with one stream-shaped
log every 10s: fps + MB/s + socket.write p50/p95/max + drop count
+ backpressured flag.
- pushSnapshot (one-shot first-paint) still uses the HTTP /frame
endpoint — small and infrequent, not worth reworking.
- postJSON still uses node:http for /state and /config.
State machine status flags
--------------------------
Extended boot-time flags array from 6 → 7 slots so the stream
server's bring-up shows up alongside ETH/MDNS/HTTP. Layout is now
E M H S D J T (was E M H D J T).
Diffstat (limited to 'main/app_main.c')
| -rw-r--r-- | main/app_main.c | 7 |
1 files changed, 6 insertions, 1 deletions
diff --git a/main/app_main.c b/main/app_main.c index 00a77bb..a6174c8 100644 --- a/main/app_main.c +++ b/main/app_main.c @@ -7,6 +7,7 @@ #include "nvs_config.h" #include "state_client.h" #include "state_machine.h" +#include "stream_server.h" #include "touch.h" #include "viewport_state.h" @@ -52,7 +53,7 @@ void app_main(void) // don't get a DHCP lease. mDNS + HTTP advertise / bind anyway and will // start serving the moment the link comes up. // ------------------------------------------------------------------ - char flags[7] = { '-','-','-','-','-','-', 0 }; // E M H D J T + char flags[8] = { '-','-','-','-','-','-','-', 0 }; // E M H S D J T esp_err_t eth_err = net_eth_init(); mark(eth_err, 'E', &flags[0]); @@ -71,6 +72,10 @@ void app_main(void) ESP_ERROR_CHECK(state_client_init()); mark(mdns_service_start(), 'M', &flags[1]); mark(http_api_start(), 'H', &flags[2]); + // Data plane: raw TCP on :81 for back-to-back JPEG streaming, + // bypassing per-frame HTTP overhead. /frame HTTP stays alive for + // the snapshot one-shot and for curl debugging. + mark(stream_server_start(81), 'S', &flags[3]); // ------------------------------------------------------------------ // Display + I²C-bound peripherals run on their own task. ESP-IDF's |
