Files
Behavision/tests/test_capture.py
Suriyakumarvijayanayagam ffae7e45d5 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
2026-09-04 16:59:27 +05:30

109 lines
3.3 KiB
Python

import numpy as np
from behavision.capture import VideoSource
def _source_with_frame(ts=100.0):
"""VideoSource opens nothing until run(), so the frame slot can be
populated directly — no camera needed."""
src = VideoSource("cam1", 0)
src._frame = np.zeros((4, 4, 3), dtype=np.uint8)
src._frame_ts = ts
return src
def test_latest_since_skips_an_already_seen_frame():
src = _source_with_frame(ts=100.0)
frame, ts = src.latest_since(100.0)
assert frame is None
assert ts == 100.0
def test_latest_since_returns_a_newer_frame():
src = _source_with_frame(ts=100.0)
frame, ts = src.latest_since(99.0)
assert frame is not None
assert ts == 100.0
def test_latest_since_returns_a_copy():
src = _source_with_frame()
frame, _ = src.latest_since(0.0)
frame[0, 0, 0] = 255
assert src._frame[0, 0, 0] == 0
def test_latest_since_with_no_frame_yet():
frame, ts = VideoSource("cam1", 0).latest_since(0.0)
assert frame is None
assert ts == 0.0
def test_first_connect_is_not_counted_as_a_reconnect():
assert VideoSource("cam1", 0).stats()["reconnects"] == 0
def _elapsed(fn):
import time
t = time.perf_counter()
result = fn()
return result, time.perf_counter() - t
def test_probe_fails_fast_on_an_unroutable_host():
"""The Test button must answer, not hang. cv2.VideoCapture blocks in its
constructor on TCP connect (~75s OS timeout), so a pre-flight check is the
only thing that bounds this."""
from behavision.capture import probe_source
result, seconds = _elapsed(lambda: probe_source(
"rtsp://10.255.255.1:554/x", connect_timeout=1.0))
assert result["ok"] is False
assert "10.255.255.1:554" in result["error"]
assert seconds < 5, f"took {seconds:.1f}s - the pre-flight is not working"
def test_probe_reports_connection_refused_immediately():
from behavision.capture import probe_source
result, seconds = _elapsed(lambda: probe_source("rtsp://127.0.0.1:1/x"))
assert result["ok"] is False
assert seconds < 5
def test_tcp_precheck_passes_through_non_url_sources():
"""A webcam index has no host to check; it must not be blocked."""
from behavision.capture import _tcp_reachable
assert _tcp_reachable(0, 1.0) == (True, "")
assert _tcp_reachable("not-a-url", 1.0)[0] is True
def test_the_probe_reports_the_stream_codec():
"""Which codec a camera emits decides whether head office can ever show
TRUE live video from it: a browser plays H.264 everywhere and H.265 only on
some platforms, so a passthrough relay cannot rely on H.265.
It is reported because otherwise the only way to learn it is to read RTSP by
hand — which is how it was found on the office camera, whose stream paths
end in ".264" while both channels emit H.265.
"""
from behavision.capture import _fourcc
class Cap:
def __init__(self, v):
self.v = v
def get(self, _):
return self.v
def code(text):
return sum(ord(c) << (8 * i) for i, c in enumerate(text))
assert _fourcc(Cap(code("hevc"))) == "hevc"
assert _fourcc(Cap(code("h264"))) == "h264"
# Nothing to report is "" rather than a misleading value: a camera that
# does not say must not read as one that said something.
assert _fourcc(Cap(0)) == ""
assert _fourcc(Cap(-1)) == ""