mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-02 15:35:09 -04:00
The cached snapshot was framed when the status last changed, so after a generation bump or further events a new consumer received it stamped with a stale generation and an old event-sequence baseline. Keep only the body and frame it in AcceptClient, so the header carries the generation and sequence in effect at the moment of connection. Tests: a snapshot cached at generation 0 with no events is delivered to a later consumer with the current generation and sequence. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
168 lines
8.6 KiB
ReStructuredText
168 lines
8.6 KiB
ReStructuredText
Monitor Stream Socket
|
|
=====================
|
|
|
|
Every monitor's ``zmc`` process serves the monitor's compressed media
|
|
(video access units and audio packets, as received from the camera) on a
|
|
local unix domain socket::
|
|
|
|
ZM_PATH_SOCKS/stream_{monitor_id}.sock e.g. /run/zm/stream_1.sock
|
|
|
|
The socket replaces the per-monitor media FIFOs
|
|
(``video_fifo_{id}.{codec}`` / ``audio_fifo_{id}.{codec}``) that previous
|
|
versions created when the RTSPServer option was enabled. Unlike the FIFOs,
|
|
the socket is always on, carries both streams on one connection, supports
|
|
multiple simultaneous consumers, delivers codec parameters in a handshake,
|
|
and makes packet loss observable per consumer. The same connection also
|
|
carries monitor lifecycle events (capture faults and analysis-state
|
|
changes) as a push channel. ZoneMinder's own ``zm_rtsp_server`` is a
|
|
consumer of this socket.
|
|
|
|
The path follows a fixed convention, so consumers can derive it from
|
|
``ZM_PATH_SOCKS`` (zm.conf) and the monitor id. It is also published in the
|
|
monitor's shared-memory ``stream_socket_path`` field (exposed by the web
|
|
Monitor object and the ``ZoneMinder::Memory`` Perl module) so a reader with
|
|
shm access does not need the convention or the producer's zm.conf. Either
|
|
way, connect with retry — the socket appears when zmc starts and survives
|
|
camera reconnects.
|
|
|
|
Only cameras that deliver encoded packets (the Ffmpeg source and anything
|
|
built on it) announce a media stream. Sources that hand zmc decoded images
|
|
(V4L2, MJPEG over HTTP, VNC) have no encoded stream to forward, so their
|
|
socket sends no HELLO or MEDIA and carries lifecycle events only.
|
|
|
|
Access control
|
|
--------------
|
|
|
|
Sockets are created with mode 0660, owned by the ZoneMinder user, with the
|
|
group taken from ``ZM_STREAM_SOCKET_GROUP`` (conf.d, defaults to the web
|
|
group). Grant a service access by group membership. Optionally,
|
|
``ZM_STREAM_SOCKET_ALLOWED_UIDS`` (comma-separated numeric uids) restricts
|
|
connections using kernel-verified peer credentials (``SO_PEERCRED`` on
|
|
Linux, ``getpeereid`` on the BSDs and macOS).
|
|
|
|
Per-consumer queue limits are tunable in conf.d:
|
|
``ZM_STREAM_SOCKET_MAX_CLIENTS`` (default 8),
|
|
``ZM_STREAM_SOCKET_QUEUE_BYTES`` (8 MiB),
|
|
``ZM_STREAM_SOCKET_QUEUE_MSGS`` (256) and
|
|
``ZM_STREAM_SOCKET_STALL_SECS`` (10). A consumer that exceeds its queue has
|
|
its oldest media messages dropped (never HELLO), observable as sequence
|
|
gaps and in STATS; a consumer accepting no data while its queue is full is
|
|
disconnected. A slow consumer never affects zmc or other consumers.
|
|
|
|
Wire protocol (version 1)
|
|
-------------------------
|
|
|
|
All integers are little-endian. Every message starts with a 24-byte fixed
|
|
header::
|
|
|
|
u32 length bytes following this field (20 + payload size)
|
|
u8 version protocol version, 1
|
|
u8 type message type, below
|
|
u8 stream 0 = video, 1 = audio, 2 = monitor (EVENT frames)
|
|
u8 flags bit 0: keyframe (video); other bits reserved, 0
|
|
u32 sequence per-stream, counts every message produced
|
|
u32 generation stream epoch; a bump means re-init the decoder
|
|
i64 pts_us signed microseconds (AV_TIME_BASE_Q), shared clock;
|
|
AV_NOPTS_VALUE (0x8000000000000000) means unknown
|
|
|
|
``sequence`` counts messages *produced*, including any dropped from a slow
|
|
consumer's queue, so loss appears as gaps. ``generation`` increments when
|
|
stream parameters change (camera reconfigure, a stream added or removed);
|
|
fresh HELLOs follow, sequences restart at 0 and the cached keyframe is
|
|
dropped. zmc applies both streams' parameters in one step, so a change to
|
|
audio and video together is a single new generation and no generation ever
|
|
pairs one stream's new parameters with the other's old ones. Generations restart at 0 when zmc restarts, so a consumer should
|
|
key on the HELLOs it receives after a (re)connect, not on the number alone.
|
|
|
|
Message types:
|
|
|
|
``0x01 HELLO``
|
|
Sent per stream on connect and again on every generation bump, audio
|
|
first: the video HELLO is always the last HELLO of a generation, so on
|
|
receiving it a consumer has that generation's complete parameter set (a
|
|
stream not re-announced by then is gone and its packets stop). A
|
|
generation that carries no video HELLO means the source currently has no
|
|
video; a consumer that needs video waits for the next generation. The
|
|
payload is a TLV list (u8 tag, u16 length, value; unknown tags must be
|
|
skipped): ``0x01`` codec id (u32, AVCodecID), ``0x02`` extradata (raw
|
|
``codecpar->extradata``: SPS/PPS/VPS for H.26x, AudioSpecificConfig for
|
|
AAC, sequence header OBU for AV1), ``0x03``/``0x04`` width/height (u32),
|
|
``0x05``/``0x06`` fps numerator/denominator (u32), ``0x07``/``0x08``
|
|
sample rate/channels (u32), ``0x09``/``0x0A`` profile/level (u32).
|
|
|
|
``0x02 MEDIA``
|
|
One complete video access unit or one audio packet (raw, not
|
|
ADTS-wrapped — the HELLO extradata makes wrapping unnecessary). The
|
|
bytes are exactly what the camera's demuxer produced, so the H.264/H.265
|
|
NAL framing follows the source: RTSP cameras deliver Annex B start
|
|
codes, while MP4/MKV file sources deliver AVCC length-prefixed NALs. A
|
|
consumer can tell which from the HELLO extradata (an AVCC
|
|
``avcC``/``hvcC`` record starts with ``0x01``; Annex B parameter sets
|
|
start with ``00 00 00 01``). ``zm_rtsp_server`` handles Annex B only.
|
|
|
|
``0x03 KEYFRAME``
|
|
Sent once after HELLO to a newly connected consumer: the most recent
|
|
cached video keyframe access unit, carrying its original pts. Lets a
|
|
consumer render a first frame immediately instead of waiting up to a
|
|
GOP; treat the next MEDIA keyframe as the stream anchor. The cache is
|
|
cleared when the camera connection closes, so nothing is replayed from
|
|
a previous capture session.
|
|
|
|
``0x04 STATS``
|
|
Periodic (default every 5 s): u64 messages sent, u64 messages dropped
|
|
for this consumer.
|
|
|
|
``0x05 BYE``
|
|
zmc is shutting the stream down; the close that follows is not an error.
|
|
|
|
``0x06 EVENT``
|
|
A monitor lifecycle event (``stream`` is ``2``). This is a push channel
|
|
for capture-fault and analysis-state changes, independent of media: it
|
|
flows even while the camera is disconnected and the media streams are
|
|
stalled. ``sequence`` is a per-monitor event counter (its own series, not
|
|
reset by a media generation bump), ``generation`` is the media epoch in
|
|
effect at emission for correlation, and ``pts_us`` is 0 when no media is
|
|
flowing — the event's own timestamp travels in a TLV.
|
|
|
|
The payload is a ``u16`` event code followed by a TLV tail (u8 tag, u16
|
|
length, value; unknown tags skipped). Event codes::
|
|
|
|
0x0001 snapshot current health + state, on connect
|
|
0x0101 connection_failed camera connect failed
|
|
0x0102 connection_restored
|
|
0x0103 prime_capture_failed could not prime the capture source
|
|
0x0104 prime_capture_restored
|
|
0x0105 capture_failed pre/capture/post capture failed
|
|
0x0106 capture_resumed pipeline recovered after any fault
|
|
0x0201 state_changed analysis state transition
|
|
|
|
TLV tags: ``0x01`` wall_clock_us (u64, unix-epoch microseconds — the
|
|
timestamp to surface), ``0x02`` message (utf8 detail), ``0x03`` state_id
|
|
(u32, current state), ``0x04`` prev_state_id (u32, for state_changed),
|
|
``0x05`` detail (u32, errno / ffmpeg error code), ``0x06`` state_name
|
|
(utf8, e.g. ``IDLE``/``ALARM``), ``0x07`` health_code (u16, the active
|
|
fault code carried by a faulted snapshot; absent or 0 means healthy).
|
|
|
|
``snapshot`` is sent to every consumer on connect (after the HELLOs, the
|
|
events analogue of the cached KEYFRAME) and is refreshed on every health
|
|
or state change, so a late subscriber learns current status without
|
|
waiting for the next transition. Its ``sequence`` is the number of events
|
|
produced so far, i.e. the sequence the next broadcast EVENT will carry; the
|
|
snapshot is a state message, not an event in that series, so a consumer
|
|
seeds its gap tracking from it and must not treat the following EVENT with
|
|
the same sequence as a duplicate. Both that sequence and the header
|
|
``generation`` are stamped when the consumer connects, so they describe
|
|
the moment of connection rather than the last status change. The
|
|
capture-fault edges are emitted by
|
|
zmc once per transition; because the socket survives camera reconnects,
|
|
``connection_failed`` is observable exactly when media has stopped.
|
|
|
|
There are no client-to-server messages in version 1; zmc ignores inbound
|
|
bytes.
|
|
|
|
The reference encoder/decoder lives in ``src/zm_stream_socket_protocol.h``;
|
|
``src/zm_stream_socket_client.cpp`` is a reusable C++ consumer with
|
|
callbacks for HELLO, MEDIA/KEYFRAME, STATS, EVENT, BYE and disconnect, and
|
|
``tools/zm_stream_socket_dump.py`` is a dependency-free Python example that
|
|
prints every message.
|