src.nth.io/

summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorLuke Hoersten <[email protected]>2026-06-20 10:30:24 -0500
committerLuke Hoersten <[email protected]>2026-06-20 10:30:24 -0500
commit496da49dd5ce4a3bfc4b5df84ce5c7f7bdbcf504 (patch)
tree1aecd053ebf9d82f1e23037cb992dad1c0def64f /main
parentcebd60d4be74ddac9c18a7b08eef9ec00cbc31f5 (diff)
"paint the latest, drop the rest" — FIONREAD skip + Scrypted backpressure-blind + lwIP TCP window bump
Three changes that work together to make the stream "always paint what's freshest, never sit on a stale frame": #1 — Firmware FIONREAD skip in stream_server Right after the body of frame N comes off the wire (and before we unlock the decoder + spend ~6ms on decode + paint), check the kernel receive buffer with ioctl(FIONREAD). If at least one more header (8 bytes) is queued, frame N is no longer the freshest possible — skip its decode + paint and loop back to read frame N+1. The TCP recv cost is unavoidable (bytes still have to cross the wire) but the decoder + paint cost is saved on every superseded frame. Glass-to-glass latency on the latest frame drops by however many frames had backed up. #2 — Scrypted: keep writing past kernel-buffer backpressure Previously: when sock.write() returned false we dropped the next ffmpeg frame at source. New: we keep writing through. Node buffers internally; under our load (~16 MB/s ffmpeg → ~5-7 MB/s firmware) the buffer rarely exceeds a frame or two. With the firmware now silently skipping decode on backed-up frames (#1), excess frames get shed for free on the device side. Scrypted's job is just to hand the firmware the freshest bytes as fast as possible. socketBackpressured is still tracked for the diagnostic log. #3 — lwIP TCP window bump (revisiting earlier regression) The previous attempt at LWIP_TCP_WND_DEFAULT=32k regressed under HTTP because every /frame opened a fresh socket and we paid the slow-start cost repeatedly. The streaming pivot eliminated that: the socket is long-lived, slow-start runs exactly once, then we ride the full window for the rest of the session. Bumping to 65535 (max for stock lwIP), SND_BUF to match, RECVMBOX to 16, SACK on. Expected: recv throughput ceiling moves up from ~5.3 MB/s, which directly raises the fps ceiling (recv is currently 37ms of the 43ms per-frame total).
Diffstat (limited to 'main')
-rw-r--r--main/stream_server.c14
1 files changed, 14 insertions, 0 deletions
diff --git a/main/stream_server.c b/main/stream_server.c
index 7876968..10d1e1b 100644
--- a/main/stream_server.c
+++ b/main/stream_server.c
@@ -9,6 +9,7 @@
#include "freertos/task.h"
#include "lwip/netdb.h"
#include "lwip/sockets.h"
+#include "sys/ioctl.h"
#include "display.h"
#include "jpeg_decoder.h"
@@ -132,6 +133,19 @@ static void handle_client(int fd, const char *peer)
continue;
}
+ // "Always paint the latest" — if the socket already has more
+ // bytes queued in the kernel receive buffer, at least one more
+ // header is on the way. The frame we just finished receiving
+ // is no longer the freshest possible; skip its decode+paint
+ // (saves ~6ms per skip) and loop straight to the next header.
+ // This trades some intermediate frames for lower
+ // glass-to-glass latency on the latest one.
+ int queued = 0;
+ if (ioctl(fd, FIONREAD, &queued) == 0 && queued >= HEADER_BYTES) {
+ jpeg_decoder_unlock();
+ continue;
+ }
+
size_t back_size = 0;
void *back = display_back_buffer(&back_size);
uint16_t w = 0, h = 0;