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.
