Skip to content
9 changes: 8 additions & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ API Wrappers
^^^^^^^^^^^^^
- pyzm is a python wrapper for the ZoneMinder APIs. It supports both the legacy and new token based API, as well as ZM logs/ZM shared memory support. See `its project site <https://github.com/pliablepixels/pyzm/>`__ for more details. Documentation is `here <https://pyzm.readthedocs.io/en/latest/>`__.

Additional API documentation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. toctree::
:maxdepth: 1

api_monitor_websocket

API evolution
^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -713,4 +721,3 @@ There are several details that haven't yet been documented. Till they are, here
* If you still can't find an answer, post your question in the `forums <https://forums.zoneminder.com/index.php>`__ (not the github repo).



202 changes: 202 additions & 0 deletions docs/api_monitor_websocket.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
Monitor Websocket API
=====================

ZoneMinder can expose live monitor data directly from ``zmc`` over a websocket
connection.

Overview
^^^^^^^^

Each monitor listens on:

::

ZM_MIN_STREAMING_PORT + MonitorId

For example, if ``ZM_MIN_STREAMING_PORT`` is ``30000`` and the monitor id is
Comment thread
AJ0070 marked this conversation as resolved.
Outdated
``5``, the websocket endpoint is:

::

ws://your-server:30005/

This requires ``Options -> Network -> MIN_STREAMING_PORT`` to be configured and
the web server or reverse proxy to allow those ports.

Comment thread
AJ0070 marked this conversation as resolved.
.. warning::

The monitor websocket endpoint can expose live camera data to any client
that can reach the monitor's streaming port. This transport does not provide
access control by itself, so do not expose these ports directly to
untrusted networks. Restrict access with firewall rules and/or place the
endpoint behind a reverse proxy that enforces authentication and
authorization.

Connection model
^^^^^^^^^^^^^^^^

The websocket server is created by ``zmc`` and runs independently per monitor.

Clients may:

* request one response
* subscribe to repeated updates
* unsubscribe later

Text frames carry JSON control and metadata messages. Binary frames carry the
requested ``jpeg``, ``raw``, or ``h264`` payload bytes.

Commands
^^^^^^^^

One-shot status request:

::

{"command":"status","request_id":"optional-id"}

One-shot image request:

::

{"command":"image","format":"jpeg","request_id":"optional-id"}

Supported image formats are:

* ``jpeg``
* ``raw``
* ``h264``

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really sure what an h264 image would be. image should be limited to actual image formats. I would be tempted to say long term that we should be able to return any image format that ffmpeg supports. Realistically raw needs to be either rgba, yuv etc. For now, because I intend to deprecate 24bit support, let's say yuv420p (which is most likely what we will be getting from a decoder), or rgba. Honeslty no one is going to want anything other than jpeg.


Status subscription:

::

{"command":"subscribe","topic":"status","interval_ms":1000}

Event subscription:

::

{"command":"subscribe","topic":"events"}

Image subscription:

::

{"command":"subscribe","topic":"image","format":"jpeg","interval_ms":1000}

Unsubscribe:

::

{"command":"unsubscribe","topic":"status"}

or:

::

{"command":"unsubscribe","topic":"events"}

or:

::

{"command":"unsubscribe","topic":"image"}

Status messages
^^^^^^^^^^^^^^^

Status replies are JSON text frames with fields such as:

* ``monitor_id``
* ``monitor_name``
* ``connected``
* ``shm_valid``
* ``state`` / ``state_id``
* ``capture_fps``
* ``analysis_fps``
* ``capture_bandwidth``
* ``image_count``
* ``signal``
* ``last_event_id``

Event messages
^^^^^^^^^^^^^^

Event subscriptions receive JSON text frames with:

* ``type = "event"``
* ``monitor_id``
* ``event``
* ``message``

These are queue-based notifications generated from capture-side failures and
recovery transitions so the capture loop does not block on websocket clients.

Image metadata and binary payloads
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Every image or video payload is sent as two websocket frames:

1. A JSON text metadata frame
2. A binary frame containing the payload bytes

The metadata frame includes:

* ``type = "image"``
* ``request_id``
* ``format``
* ``content_type``
* ``monitor_id``
* ``width``
* ``height``

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

due to alignment/padding, the raw data may have lines longer than width. Will need to commmunicate that as well.

* ``colours``
* ``subpixel_order``
* ``image_count``
* ``sequence``
* ``keyframe``
* ``payload_bytes``

JPEG and raw image behavior
^^^^^^^^^^^^^^^^^^^^^^^^^^^

``jpeg`` and ``raw`` payloads are generated from the latest image in the
monitor shared-memory ring buffer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For simplicity sake, you should be able to use the packetqueue for these as well. Everything in shm should also be in packetqueue.


For subscription mode, ``interval_ms`` controls how often the server checks for
and sends a newer frame.

H264 behavior

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should be careful here using h264 to really mean a video stream =. I think we need clear separation between asking for an image, and asking for a video stream. Please note that I would choose to use mjpeg when we are talking about a stream of jpegs. So we are talking about a video stream, with some codec. We already will need support for h265 and av1. While ideally we should be able to ask for anything (and have it transcode) 99% of the time we will want to simply request the passthrough video data in whatever codec it happens to be in.

^^^^^^^^^^^^^

``h264`` delivery uses the monitor packet queue, not the shared-memory image
buffer.

One-shot ``h264`` requests return a decodable packet snapshot:

* the payload is the latest queued H.264 packet
* if the packet is a keyframe, codec extradata is prepended before the packet
bytes

``h264`` subscriptions stream queued packets in order starting from the latest
available keyframe in the queue. This gives new subscribers a decodable start
point and avoids dropping interdependent packets.
Comment thread
AJ0070 marked this conversation as resolved.
Outdated

For ``h264`` subscriptions:

* packets are pushed in queue order
* ``interval_ms`` is ignored
* ``sequence`` tracks the packet queue order
* ``keyframe`` indicates whether the payload begins a new decodable segment

Errors
^^^^^^

Protocol errors are returned as JSON text frames:

::

{"type":"error","message":"..."}

Unsupported payload formats, unavailable monitor data, or malformed commands
return an error frame instead of a binary payload.
4 changes: 2 additions & 2 deletions docs/userguide/options/options_network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ HTTP_UA - When ZoneMinder communicates with remote cameras it will identify itse

HTTP_TIMEOUT - When retrieving remote images ZoneMinder will wait for this length of time before deciding that an image is not going to arrive and taking steps to retry. This timeout is in milliseconds (1000 per second) and will apply to each part of an image if it is not sent in one whole chunk.

MIN_STREAMING_PORT - ZoneMinder supports a concept called multi-port streaming. The core concept is that modern browsers like Chrome limit the number of simultaneous connections allowed from a specific domain (host name+port). In the case of Chrome this value is 6, which means you can't see more than 6 simultaneous streams from your server at one time. However, if the streams originated from different ports (or sub domains), this limitation would not apply. When you enable this option with a value (in this case, ``30000``), the streams from the monitors will originate from ``30000`` plus the monitor ID, effectively overcoming this limitation. **Note that this also needs additional setup your webserver configuration before this can start to work**. Please refer to `this article <https://medium.com/zmninja/multi-port-storage-areas-and-more-d5836a336c93>`__ on how to setup multi port streaming on Apache.
MIN_STREAMING_PORT - ZoneMinder supports a concept called multi-port streaming. The core concept is that modern browsers like Chrome limit the number of simultaneous connections allowed from a specific domain (host name+port). In the case of Chrome this value is 6, which means you can't see more than 6 simultaneous streams from your server at one time. However, if the streams originated from different ports (or sub domains), this limitation would not apply. When you enable this option with a value (in this case, ``30000``), the streams from the monitors will originate from ``30000`` plus the monitor ID, effectively overcoming this limitation. The same port base is also used by the monitor websocket transport in ``zmc``. Each monitor websocket listens on ``MIN_STREAMING_PORT + MonitorId``. If another listener already owns that port range, the websocket server will not be able to bind. **Note that this also needs additional setup your webserver configuration before this can start to work**. Please refer to `this article <https://medium.com/zmninja/multi-port-storage-areas-and-more-d5836a336c93>`__ on how to setup multi port streaming on Apache.
Comment thread
AJ0070 marked this conversation as resolved.
Outdated

MIN_RTP_PORT - When ZoneMinder communicates with MPEG4 capable cameras using RTP with the unicast method it must open ports for the camera to connect back to for control and streaming purposes. This setting specifies the minimum port number that ZoneMinder will use. Ordinarily two adjacent ports are used for each camera, one for control packets and one for data packets. This port should be set to an even number, you may also need to open up a hole in your firewall to allow cameras to connect back if you wish to use unicasting.

MAX_RTP_PORT - When ZoneMinder communicates with MPEG4 capable cameras using RTP with the unicast method it must open ports for the camera to connect back to for control and streaming purposes. This setting specifies the maximum port number that ZoneMinder will use. Ordinarily two adjacent ports are used for each camera, one for control packets and one for data packets. This port should be set to an even number, you may also need to open up a hole in your firewall to allow cameras to connect back if you wish to use unicasting. You should also ensure that you have opened up at least two ports for each monitor that will be connecting to unicasting network cameras.
MAX_RTP_PORT - When ZoneMinder communicates with MPEG4 capable cameras using RTP with the unicast method it must open ports for the camera to connect back to for control and streaming purposes. This setting specifies the maximum port number that ZoneMinder will use. Ordinarily two adjacent ports are used for each camera, one for control packets and one for data packets. This port should be set to an even number, you may also need to open up a hole in your firewall to allow cameras to connect back if you wish to use unicasting. You should also ensure that you have opened up at least two ports for each monitor that will be connecting to unicasting network cameras.
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ set(ZM_BIN_SRC_FILES
zm_uri.cpp
zm_user.cpp
zm_utils.cpp
zm_websocket.cpp
zm_videostore.cpp
zm_zone.cpp
zm_storage.cpp)
Expand Down
Loading
Loading