Live view runs at the camera's real rate, and reports why it is MJPEG

4 fps was not "live", and it was a number I picked rather than measured.
The engine actually produces ~12 distinct frames a second, so most of it
was being left on the floor.

Now: poll a little ahead of the engine and drop frames identical to the
last one by hash. Measured end to end - 131 frames in 10 s, 13.1 fps,
20.3 KB each, 259 KB/s, zero duplicates. Every byte on the wire is a
picture the viewer has not seen, and the rate follows the camera instead
of a constant.

Also records why this is MJPEG rather than passing the camera's own
compressed video through, which would be smoother, cheaper and use no
CPU. Probed the office camera: main 2304x1296@15, sub 800x448@15 - and
BOTH are H.265, despite stream paths ending in ".264". Browsers play
H.264 everywhere and H.265 only on some platforms, so passthrough cannot
rely on it, and transcoding HEVC on the shop PC would put a video encoder
on the machine already doing the recognition.

So probe_source now reports `codec`. It decides what is possible, an
installer can usually change it, and otherwise the only way to learn it is
to read RTSP by hand - which is how this was found.

The RTSP libraries used to establish that are NOT kept: they were only
ever imported by a spike test, and two large dependencies in a shipped
binary to answer a question OpenCV already knows is a bad trade. Their
`go get` had also silently bumped the agent to go 1.25 and broken the
desktop build, which is its own argument.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HViLj9gYNRtSr7YVZmW5sn
This commit is contained in:
2026-09-04 16:59:27 +05:30
parent 18686cbceb
commit ffae7e45d5
7 changed files with 140 additions and 30 deletions

View File

@@ -2192,13 +2192,45 @@ is the shape of everything else in this system — so `LiveHub` + `cameras.Live`
relay frames the other way: head office holds a poll open, the agent asks "is
anyone watching?", and pushes JPEGs up for exactly as long as somebody is.
**It is ~4 frames a second of 640 px JPEG, and the UI says so.** Measured on the
office camera: the full frame is 98 KB, re-encoded at 640/q60 it is 20.8 KB, so
one watcher costs ~83 KB/s. True 25 fps video would need WebRTC and a TURN
server; for *"what does that camera see right now"* — which is the question
somebody at head office is actually asking — a few frames a second is what the
question requires, and a label saying "about 4 frames a second" is better than
letting somebody conclude the camera stutters.
**It is ~13 frames a second of 640 px JPEG.** Measured end to end on the office
camera: 131 frames in 10 s, 20.3 KB each, **259 KB/s**, and zero duplicates.
The rate is not a guess. The engine re-serves its latest frame until the
pipeline produces a new one, so polling faster than it encodes returns the same
picture: 93 polls in 6 s yielded 72 distinct frames. So the relay polls a little
ahead of the engine and **drops frames identical to the last one by hash** —
which lets the rate follow the camera rather than a constant, and means every
byte on the wire is a picture the viewer has not seen.
### Why this is MJPEG and not the camera's own H.264
The obviously better design is passthrough: every CCTV camera already produces
compressed video, and its **sub-stream** is exactly the right size for a live
view. Probed on the office camera: main `/ch0_0.264` is 2304×1296 @ 15 fps, sub
`/ch0_1.264` is **800×448 @ 15 fps**. Relaying that untouched would be smoother
than this, cost less bandwidth, and use no CPU at all — no decode, no encode.
**It cannot be done on this camera, and the reason is worth recording: both
streams are H.265.** The file names end in `.264`; the codec is HEVC. A browser
plays H.264 everywhere and H.265 only on some platforms, so a passthrough relay
cannot rely on it — and transcoding HEVC→H.264 on the shop PC would put a video
encoder on the machine that is already doing the recognition.
So the choice is not MJPEG-versus-video in the abstract. It is: **re-encode
frames and work on every camera, or pass through and work only on H.264
cameras.** This does the first. Passthrough (RTSP → fMP4 → Media Source
Extensions, no re-encode) is a well-understood build on top of the same relay
and is the right upgrade for an estate of H.264 cameras — including this one, if
its sub-stream is switched to H.264 in the camera's own settings.
`probe_source` therefore reports `codec`, because it decides what is possible
and an installer can usually change it. Otherwise the only way to learn it is to
read RTSP by hand, which is how this was found.
True sub-second video with no re-encode at any codec is WebRTC. Worth noting
that the earlier claim here — that it needs a TURN server — is wrong: the server
has a public address, so a shop PC behind NAT connects to it directly and TURN
is only needed when *neither* side is reachable.
The browser cannot be pointed straight at the shop PC even on one LAN: the
engine's API is Basic-authenticated with a credential it generates locally and
@@ -2245,6 +2277,9 @@ Other decisions worth keeping:
has OpenCV open and the frame decoded; a scaler in the agent would be the same
work twice. On demand only — a camera nobody watches must not pay for a second
encode it will never use.
- **Duplicate frames are dropped by hash before they are sent.** Without it a
fifth of the bandwidth was the same picture twice, and the poll rate could not
safely run ahead of the engine.
- **The Live button is offered even when the card says the camera is down.**
`connected` is head office's last report and can be two minutes stale, so
gating on it hid the button during every reconnect — and "is that camera