Ctrl K
ComunidadGuías…

src/zm_monitor.h

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
//
// ZoneMinder Monitor Class Interfaces, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//

#ifndef ZM_MONITOR_H
#define ZM_MONITOR_H

#include "zm_define.h"
#include "zm_camera.h"
#include "zm_analysis_thread.h"
#include "zm_poll_thread.h"
#include "zm_decoder_thread.h"
#include "zm_event.h"
#include "zm_fifo.h"
#include "zm_image.h"
#include "zm_mqtt.h"
#include "zm_packet.h"
#include "zm_packetqueue.h"
#include "zm_utils.h"
#include "zm_audio_detector.h"
#include "zm_zone.h"

#include <atomic>
#include <functional>
#include <list>
#include <map>
#include <memory>
#include <sys/time.h>
#include <type_traits>
#include <vector>
#if HAVE_LIBCURL
#include <curl/curl.h>
#endif  // HAVE_LIBCURL

#include "zm_monitor_onvif.h"

class Group;
class MonitorLinkExpression;
class StreamSocket;

#define SIGNAL_CAUSE "Signal"
#define MOTION_CAUSE "Motion"
#define AUDIO_CAUSE "Audio"
#define LINKED_CAUSE "Linked"


//
// This is the main class for monitors. Each monitor is associated
// with a camera and is effectively a collector for events.
//
class Monitor : public std::enable_shared_from_this<Monitor> {
  friend class MonitorStream;
  friend class MonitorLinkExpression;
  friend class ONVIF;

 public:
  typedef enum {
    QUERY=0,
    CAPTURE,
    ANALYSIS
  } Purpose;

  typedef enum {
    CAPTURING_NONE=1,
    CAPTURING_ONDEMAND,
    CAPTURING_ALWAYS
  } CapturingOption;

  typedef enum {
    ANALYSING_NONE=1,
    ANALYSING_ALWAYS
  } AnalysingOption;

  typedef enum {
    ANALYSIS_PRIMARY=1,
    ANALYSIS_SECONDARY
  } AnalysisSourceOption;

  typedef enum {
    ANALYSISIMAGE_FULLCOLOUR=1,
    ANALYSISIMAGE_YCHANNEL
  } AnalysisImageOption;

  typedef enum {
    RECORDING_NONE=1,
    RECORDING_ONMOTION,
    RECORDING_ALWAYS
  } RecordingOption;

  typedef enum {
    RECORDING_PRIMARY=1,
    RECORDING_SECONDARY,
    RECORDING_BOTH
  } RecordingSourceOption;

  typedef enum {
    DECODING_NONE=1,
    DECODING_ONDEMAND,
    DECODING_KEYFRAMES,
    DECODING_KEYFRAMESONDEMAND,
    DECODING_ALWAYS
  } DecodingOption;

  typedef enum {
    HLS,
    MSE,
    WEBRTC
  } RTSP2WebOption;

  typedef enum {
    RESTREAM=1,
    CAMERA_DIRECT_PRIMARY,
    CAMERA_DIRECT_SECONDARY
  } StreamChannelOption;

  typedef enum {
    LOCAL=1,
    REMOTE,
    FILE,
    FFMPEG,
    LIBCURL,
    VNC,
  } CameraType;

  typedef enum {
    ROTATE_0=1,
    ROTATE_90,
    ROTATE_180,
    ROTATE_270,
    FLIP_HORI,
    FLIP_VERT
  } Orientation;

  typedef enum {
    DEINTERLACE_DISABLED = 0x00000000,
    DEINTERLACE_FOUR_FIELD_SOFT = 0x00001E04,
    DEINTERLACE_FOUR_FIELD_MEDIUM = 0x00001404,
    DEINTERLACE_FOUR_FIELD_HARD = 0x00000A04,
    DEINTERLACE_DISCARD = 0x00000001,
    DEINTERLACE_LINEAR = 0x00000002,
    DEINTERLACE_BLEND = 0x00000003,
    DEINTERLACE_BLEND_25 = 0x00000205,
    DEINTERLACE_V4L2_TOP = 0x02000000,
    DEINTERLACE_V4L2_BOTTOM = 0x03000000,
    DEINTERLACE_V4L2_ALTERNATE = 0x07000000,
    DEINTERLACE_V4L2_PROGRESSIVE = 0x01000000,
    DEINTERLACE_V4L2_INTERLACED = 0x04000000
  } Deinterlace;

  typedef enum {
    UNKNOWN = 0,
    IDLE,
    PREALARM,
    ALARM,
    ALERT
  } State;

  typedef enum {
    DISABLED,
    ENCODE,
    PASSTHROUGH,
  } VideoWriter;

  // An action this monitor performs when it alarms. The device acted on is
  // identified by target_monitor_id and is frequently NOT this monitor: the
  // point of the feature is that a camera can sound a speaker elsewhere.
  struct EventAction {
    enum TriggerOn { EVENT_START, EVENT_END, ALARM, ALARM_END, MANUAL };
    TriggerOn     trigger;
    std::string   action_type;       // zmcontrol command, e.g. audioPlay
    unsigned int  target_monitor_id;
    int           audio_file;        // -1 when the action takes no file
  };

 protected:
  typedef std::set<Zone *> ZoneSet;

  typedef enum { GET_SETTINGS=0x1, SET_SETTINGS=0x2, RELOAD=0x4, SUSPEND=0x10, RESUME=0x20 } Action;

  typedef enum { CLOSE_UNKNOWN=0, CLOSE_SYSTEM, CLOSE_TIME, CLOSE_DURATION, CLOSE_IDLE, CLOSE_ALARM } EventCloseMode;

  /* A SharedData field. zmc's capture, decoder and analysis threads, zms, zmu
   * and the web interface all read and write these while the others run, so
   * every access is a relaxed atomic: each load and store is whole and is seen
   * by the other threads, but is not ordered against anything else. Plain
   * members promised neither (a 64-bit field on i386 is two loads), and
   * ThreadSanitizer reported them. The read-modify-write operators are single
   * atomic operations, so zmu setting an action bit can't be lost to zmc
   * clearing another. It holds only the value, so the layout that Memory.pm
   * and Monitor.php read is unchanged. Copying one field to another is deleted
   * because it would not be atomic; load() one and assign it. */
  template <typename T>
  class SharedValue {
   public:
    SharedValue &operator=(const SharedValue &) = delete;
    T load() const {
      T value;
      __atomic_load(&value_, &value, __ATOMIC_RELAXED);
      return value;
    }
    void store(T value) { __atomic_store(&value_, &value, __ATOMIC_RELAXED); }
    operator T() const { return load(); }
    T operator=(T value) { store(value); return value; }
    T operator++(int) { return __atomic_fetch_add(&value_, 1, __ATOMIC_RELAXED); }
    T operator|=(T bits) { return __atomic_or_fetch(&value_, bits, __ATOMIC_RELAXED); }
    T operator&=(T bits) { return __atomic_and_fetch(&value_, bits, __ATOMIC_RELAXED); }
   private:
    // On i386 a double or 64-bit integer is only 4-aligned, and clang then calls
    // libatomic for the 8-byte operations. Every 8-byte field already sits on
    // an 8-byte offset, so this moves nothing; the offset asserts below check.
    alignas(sizeof(T)) T value_;
  };
  static_assert(sizeof(SharedValue<double>) == sizeof(double), "SharedValue must hold only the value");
  static_assert(sizeof(SharedValue<uint8_t>) == sizeof(uint8_t), "SharedValue must hold only the value");
  static_assert(std::is_trivially_copyable<SharedValue<double>>::value, "SharedData is memset and mapped raw");

  /* sizeof(SharedData) is 888 bytes on both 32bit and 64bit */
  typedef struct {
    SharedValue<uint32_t> size;                /* +0 */
    SharedValue<int32_t> last_write_index;     /* +4 */
    SharedValue<int32_t> last_read_index;      /* +8 */
    SharedValue<int32_t> image_count;          /* +12 */
    SharedValue<uint32_t> state;               /* +16 */
    /* Explicit pad. x86-64 aligns double to 8 and inserts this implicitly, but
     * the i386 SysV ABI aligns double to 4 and would not, shifting every later
     * member by 4. Making it explicit keeps the layout identical on both. */
    uint32_t epadding1;         /* +20 */
    SharedValue<double> capture_fps;           /* +24   Current capturing fps */
    SharedValue<double> analysis_fps;          /* +32   Current analysis fps */
    SharedValue<double> latitude;              /* +40 */
    SharedValue<double> longitude;             /* +48 */
    SharedValue<uint64_t> last_event_id;       /* +56 */
    SharedValue<uint32_t> action;              /* +64 */
    SharedValue<int32_t> brightness;           /* +68 */
    SharedValue<int32_t> hue;                  /* +72 */
    SharedValue<int32_t> colour;               /* +76 */
    SharedValue<int32_t> contrast;             /* +80 */
    SharedValue<int32_t> alarm_x;              /* +84 */
    SharedValue<int32_t> alarm_y;              /* +88 */
    SharedValue<uint8_t> valid;                /* +92 */
    SharedValue<uint8_t> capturing;            /* +93 */
    SharedValue<uint8_t> analysing;            /* +94 */
    SharedValue<uint8_t> recording;            /* +95 */
    SharedValue<uint8_t> signal;               /* +96 */
    SharedValue<uint8_t> format;               /* +97 */
    SharedValue<uint8_t> audio_level;          /* +98  0-100, written by the capture thread */
    SharedValue<uint8_t> audio_alarm;          /* +99  audio_level crossed AudioThreshold */
    SharedValue<uint32_t> imagesize;           /* +100 */
    SharedValue<uint32_t> last_frame_score;    /* +104 */
    SharedValue<uint32_t> audio_frequency;     /* +108 */
    SharedValue<uint32_t> audio_channels;      /* +112 */
    //uint32_t reserved3;         /* +0   */
    /*
     ** This keeps 32bit time_t and 64bit time_t identical and compatible as long as time is before 2038.
     ** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16.
     ** Because startup_time is 64bit it may be aligned to a 64bit boundary.  So it's offset SHOULD be a multiple
     ** of 8. Add or delete epadding's to achieve this.
     */
    /* Explicit pad, same reasoning as epadding1: the following union is
     * 8-aligned on x86-64 but only 4-aligned on i386. */
    uint32_t epadding2;         /* +116 */
    union {                     /* +120  */
      SharedValue<time_t> startup_time;        /* When the zmc process started.  zmwatch uses this to see how long the process has been running without getting any images */
      uint64_t extrapad1;
    };
    union {                     /* +128  */
      SharedValue<time_t> heartbeat_time;      /* Constantly updated by zmc.  Used to determine if the process is alive or hung or dead */
      uint64_t extrapad2;
    };
    union {                     /* +136  */
      SharedValue<time_t> last_write_time;
      uint64_t extrapad3;
    };
    union {                     /* +144  */
      SharedValue<time_t> last_read_time;
      uint64_t extrapad4;
    };
    union {                     /* +152  */
      SharedValue<time_t> last_viewed_time;
      uint64_t extrapad5;
    };
    union {                     /* +160  */
      SharedValue<time_t> last_analysis_viewed_time;
      uint64_t extrapad6;
    };
    uint8_t control_state[256]; /* +168 */

    char alarm_cause[256]; /* +424 */
    /* Absolute path of the per-monitor media stream socket
     ** (PATH_SOCKS/stream_{monitor_id}.sock), published so consumers discover
     ** it without hard-coding the convention or reading the producer's
     ** zm.conf. Empty until the socket is served, or if the path does not fit.
     ** Reuses the retired video_fifo_path field, same offset and size. */
    char stream_socket_path[64]; /* +680 */
    /* Formerly audio_fifo_path, retired with video_fifo_path when the media
     ** FIFOs were replaced by the stream socket. A single socket carries both
     ** streams, so there is no separate audio path to publish; this slot stays
     ** reserved. Retiring an shm field means keeping it in place at its
     ** original offset and size (never removing or reordering it, which would
     ** shift every field after it for out-of-tree readers): either give it a
     ** new meaning of the same width, as stream_socket_path above did, or leave
     ** it reserved like this. Free for a future 64-byte field. */
    char reserved_path2[64]; /* +744 */
    char janus_pin[64]; /* +808 */
    /* Analysis image ring: the annotated/analysis image is published into a
     * ring of image_buffer_count slots (reusing the alarm_images SHM region).
     * last_analysis_index is the slot most recently written (or
     * image_buffer_count as the "nothing written yet" sentinel);
     * analysis_image_count is a monotonic counter of analysis images published.
     * Appended at the end so no earlier SharedData offset shifts. */
    SharedValue<int32_t> last_analysis_index;  /* +872 */
    SharedValue<int32_t> analysis_image_count; /* +876 */
    /* Wall clock second up to which the capture thread should keep measuring
     * the audio level even though AudioDetection is off. The monitor editor's
     * level meter refreshes this while it is on screen and zmc stops decoding
     * audio once it has passed, so a reading can be watched without paying for
     * one on every monitor forever. 0 means nobody is asking.
     *
     * Carved out of analysis_pad rather than appended, so neither the 888-byte
     * total nor any existing offset moves. */
    SharedValue<uint32_t> audio_level_until;   /* +880 */
    uint32_t analysis_pad;         /* +884   keep 16-byte multiple */
    /* 888 total */
  } SharedData;
  // Cross-process ABI guard: zmc/zma/zms plus the Perl (Memory.pm) and PHP
  // (Monitor.php) SHM readers all assume this exact layout. If it changes,
  // update those readers in lockstep and bump the size below.
  //
  // The struct is naturally aligned (NOT packed). Both interior pads are now
  // declared explicitly as epadding1/epadding2, so the /* +N */ comments above
  // are the real offsets on every supported ABI. Those pads are what keep i386
  // (which aligns double and uint64_t to 4, not 8) byte-identical to x86-64;
  // without them i386 produced a 880-byte struct that disagreed with the Perl
  // and PHP readers, both of which assume 8-byte alignment unconditionally.
  static_assert(sizeof(SharedData) == 888, "SharedData layout changed; update Memory.pm and Monitor.php offsets");
  // The two members whose alignment differs between x86-64 and i386, asserted
  // individually so an ABI divergence points at the culprit rather than only
  // reporting a size mismatch. These must match the hardcoded offsets in
  // web/includes/Monitor.php and the computed ones in Memory.pm.
  static_assert(offsetof(SharedData, capture_fps) == 24, "capture_fps offset changed; update Memory.pm and Monitor.php");
  static_assert(offsetof(SharedData, startup_time) == 120, "startup_time offset changed; update Memory.pm and Monitor.php");
  static_assert(offsetof(SharedData, control_state) == 168, "control_state offset changed; update Memory.pm and Monitor.php");
  static_assert(offsetof(SharedData, audio_level_until) == 880, "audio_level_until offset changed; update Memory.pm and Monitor.php");

  enum TriggerState : uint32 {
    TRIGGER_CANCEL,
    TRIGGER_ON,
    TRIGGER_OFF
  };

  /* sizeof(TriggerData) expected to be 560 on 32bit & and 64bit */
  typedef struct {
    uint32_t size;              /* 920 */
    TriggerState trigger_state; /* 924 */
    uint32_t trigger_score;     /* 928 */
    uint32_t padding;           /* 936 */
    char trigger_cause[32];     /* 968 */
    char trigger_text[256];     /* 1224 */
    char trigger_showtext[256];
  } TriggerData;

  //TODO: Technically we can't exclude this struct when people don't have avformat as the Memory.pm module doesn't know about avformat
  //sizeOf(VideoStoreData) expected to be 4104 bytes on 32bit and 64bit
  typedef struct {
    uint32_t size;
    uint64_t current_event;
    char event_file[4096];
    timeval recording;      // used as both bool and a pointer to the timestamp when recording should begin
  } VideoStoreData;

 public:
  class MonitorLink {
   protected:
    std::shared_ptr<Monitor>  monitor;
    unsigned int zone_id;
    const Zone    *zone;
    int  zone_index;  // index into zone_scores for our zone

    std::string   name;

    bool      connected;
    time_t    last_connect_time;

#if ZM_MEM_MAPPED
    int       map_fd;
    std::string      mem_file;
#else // ZM_MEM_MAPPED
    int       shm_id;
#endif // ZM_MEM_MAPPED
    off_t     mem_size;
    unsigned char  *mem_ptr;

    SharedData  *shared_data;
    volatile TriggerData  *trigger_data;
    volatile VideoStoreData *video_store_data;
    volatile int * zone_scores;

    int        last_state;
    std::vector<Zone> zones;

   public:
    MonitorLink(std::shared_ptr<Monitor> p_monitor, unsigned int p_zone_id);
    ~MonitorLink();

    inline unsigned int Id() const { return monitor->Id(); }
    inline const char *Name() const { return name.c_str(); }

    inline bool isConnected() const { return connected && shared_data->valid; }
    inline time_t getLastConnectTime() const { return last_connect_time; }

    inline uint32_t lastFrameScore() {
      return shared_data->last_frame_score;
    }

    bool connect();
    bool disconnect();

    bool isAlarmed();
    bool inAlarm();
    bool hasAlarmed();
    int score();
  };
 protected:

  class AmcrestAPI {
#if HAVE_LIBCURL
   private:
    Monitor *parent;
    bool alarmed;
    bool healthy;
    std::string last_topic;
    std::string last_value;

    std::string response;
    CURLM *curl_multi = nullptr;
    CURL *Amcrest_handle = nullptr;
    static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp);
    std::unordered_map<std::string, std::string> alarms;
    std::mutex   alarms_mutex;
    std::mutex   response_mutex;

    void SetNoteSet(Event::StringSet &noteSet);
   public:
    explicit AmcrestAPI(Monitor *parent_);
    ~AmcrestAPI();
    int API_Connect();
    void WaitForMessage();
    int start();
    bool isAlarmed() const { return alarmed; };
    bool isHealthy() const { return healthy; };
    void setNotes(Event::StringSet &noteSet) { SetNoteSet(noteSet); };
#else
   public:
    explicit AmcrestAPI(Monitor *) {}
    void WaitForMessage() {}
    bool isAlarmed() const { return false; }
    bool isHealthy() const { return true; }
    void setNotes(Event::StringSet &) {}
#endif  // HAVE_LIBCURL
  };

  class RTSP2WebManager {
#if HAVE_LIBCURL
   protected:
    Monitor *parent;
    CURL *curl = nullptr;
    //helper class for CURL
    static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp);
    bool RTSP2Web_Healthy;
    bool Use_RTSP_Restream;
    bool ssl_verification_failed = false;
    std::string RTSP2Web_endpoint;
    std::string rtsp_username;
    std::string rtsp_password;
    std::string rtsp_restream_path;
    std::string rtsp_path;
    std::string rtsp_second_path;

   public:
    explicit RTSP2WebManager(Monitor *parent_);
    ~RTSP2WebManager();
    void load_from_monitor();
    int add_to_RTSP2Web();
    int check_RTSP2Web();
    int remove_from_RTSP2Web();
#else
   public:
    explicit RTSP2WebManager(Monitor *) {}
    void load_from_monitor() {}
    int add_to_RTSP2Web() { return 0; }
    int check_RTSP2Web() { return 1; }
    int remove_from_RTSP2Web() { return 0; }
#endif  // HAVE_LIBCURL
  };

  class Go2RTCManager {
#if HAVE_LIBCURL
    protected:
      Monitor *parent;
      // helper class for CURL
      struct transfer {
        const char *buf;
        size_t total;
        size_t uploaded;
      };
      static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp);
      static size_t ReadCallback(char *ptr, size_t size, size_t nmemb, void *data);
      std::pair<CURLcode, std::string>  CURL_PUT(const std::string &endpoint, const std::string &data) const;
      bool Go2RTC_Healthy;
      bool Use_RTSP_Restream;
      mutable bool ssl_verification_failed = false;
      std::string Go2RTC_endpoint;
      std::string rtsp_restream_path;
      std::string rtsp_restream_base_path;  // Path without auth parameter
      std::string rtsp_username;
      std::string rtsp_password;
      std::string rtsp_path;
      std::string rtsp_second_path;
      SystemTimePoint last_auth_refresh;    // Track when auth was last refreshed

    public:
      explicit Go2RTCManager(Monitor *parent_);
      ~Go2RTCManager();
      void load_from_monitor();
      int add_to_Go2RTC();
      int check_Go2RTC();
      int remove_from_Go2RTC();
      bool refresh_auth_if_needed();        // Refresh auth_hash if expired
#else
    public:
      explicit Go2RTCManager(Monitor *) {}
      void load_from_monitor() {}
      int add_to_Go2RTC() { return 0; }
      int check_Go2RTC() { return 1; }
      int remove_from_Go2RTC() { return 0; }
      bool refresh_auth_if_needed() { return false; }
#endif  // HAVE_LIBCURL
  };

  class JanusManager {
#if HAVE_LIBCURL
   protected:
    Monitor *parent;
    CURL *curl = nullptr;
    //helper class for CURL
    static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp);
    bool Janus_Healthy;
    bool Use_RTSP_Restream;
    bool ssl_verification_failed = false;
    std::string janus_session;
    std::string janus_handle;
    std::string janus_endpoint;
    std::string stream_key;
    std::string rtsp_username;
    std::string rtsp_password;
    TimePoint   rtsp_auth_time;
    std::string rtsp_path;
    std::string profile_override;
    std::uint32_t rtsp_session_timeout;

   public:
    explicit JanusManager(Monitor *parent_);
    ~JanusManager();
    void load_from_monitor();
    int add_to_janus();
    int check_janus();
    int remove_from_janus();
    int get_janus_session();
    int get_janus_handle();
    int get_janus_plugin();
#else
   public:
    explicit JanusManager(Monitor *) {}
    void load_from_monitor() {}
    int add_to_janus() { return 0; }
    int check_janus() { return 1; }
    int remove_from_janus() { return 0; }
    int get_janus_session() { return 0; }
    int get_janus_handle() { return 0; }
    int get_janus_plugin() { return 0; }
#endif  // HAVE_LIBCURL
  };


  // These are read from the DB and thereafter remain unchanged
  unsigned int    id;
  std::string     name;
  bool            deleted;
  unsigned int    server_id;          // Id of the Server object
  unsigned int    storage_id;         // Id of the Storage Object, which currently will just provide a path, but in future may do more.
  CameraType      type;
  CapturingOption capturing;          // None, OnDemand, Always
  AnalysingOption analysing;          // None, Always
  AnalysisSourceOption  analysis_source;    // Primary, Secondary
  AnalysisImageOption   analysis_image;     // FullColour, YChannel
  RecordingOption recording;          // None, OnMotion, Always
  RecordingSourceOption recording_source;   // Primary, Secondary, Both

  DecodingOption  decoding;   // Whether the monitor will decode h264/h265 packets
  bool            RTSP2Web_enabled;      // Whether we set the h264/h265 stream up on RTSP2Web
  int             RTSP2Web_type;      // Whether we set the h264/h265 stream up on RTSP2Web
  StreamChannelOption stream_channel;      // Which stream source to use: Restream, CameraDirectPrimary, or CameraDirectSecondary
  bool            Go2RTC_enabled;     // Whether we set the h264/h265 stream up on Go2RTC
  bool            Go2RTC_disable_backchannel;
  bool            janus_enabled;      // Whether we set the h264/h265 stream up on janus
  bool            janus_audio_enabled;      // Whether we tell Janus to try to include audio.
  std::string     janus_profile_override;   // The Profile-ID to force the stream to use.
  bool            restream;  // Point streaming services (Janus/Go2RTC/RTSP2Web) at the ZM RTSP output, rather than the camera directly.
  std::string     janus_pin;  // For security, we generate a pin required to view the stream.
  int             rtsp_user;          // User Id of a user to use for auth to RTSP_Server
  int             janus_rtsp_session_timeout;  // RTSP session timeout (work around for cameras that dont send ;timeout=<timeout in seconds> but do have a timeout)

  std::string protocol;
  std::string method;
  std::string options;
  std::string host;
  std::string port;
  std::string user;
  std::string pass;
  std::string path;
  std::string second_path;
  std::string device_class;

  std::string onvif_url;
  std::string onvif_events_path;
  std::string onvif_username;
  std::string onvif_password;
  std::string onvif_options;
  bool        onvif_event_listener;
  bool        use_Amcrest_API;

  std::string     device;
  int             palette;
  int             channel;
  int             format;

  int    camera_width;
  int    camera_height;
  unsigned int    width;              // Normally the same as the camera, but not if partly rotated
  unsigned int    height;             // Normally the same as the camera, but not if partly rotated
  bool            v4l_multi_buffer;
  unsigned int    v4l_captures_per_frame;
  Orientation     orientation;        // Whether the image has to be rotated at all
  unsigned int    deinterlacing;
  unsigned int    deinterlacing_value;
  std::string     decoder_name;
  std::string     decoder_hwaccel_name;
  std::string     decoder_hwaccel_device;

  // The decoder belongs here rather than to the camera: the camera's job is to
  // produce packets, and the decoder thread runs against this context for as
  // long as the monitor says so. It used to live in FfmpegCamera, where
  // camera->PrimeCapture() freed and rebuilt it underneath a decoder thread
  // still holding the pointer.
  //
  // decoder_hw_pix_fmt must outlive mVideoCodecContext: the context stores its
  // address in opaque and get_hw_format reads it back on every format
  // negotiation. Being a member of the same object gives it that lifetime.
  AVCodecContext *mVideoCodecContext;
  AVPixelFormat   decoder_hw_pix_fmt;
  AVBufferRef    *decoder_hw_device_ctx;
  bool            videoRecording;

  int             savejpegs;
  int             colours;
  VideoWriter     videowriter;
  std::string     encoderparams;
  std::string     output_codec;
  std::string     encoder;
  std::string     encoder_hwaccel_name;
  std::string     encoder_hwaccel_device;
  // Hardware device contexts used by the encoder (VideoStore), one per
  // (hwdevice type, device) and shared by every event's VideoStore rather than
  // created and destroyed per event: a per-event create/destroy opened and
  // closed /dev/dri/renderD128 each time, and on some VAAPI drivers the
  // teardown leaked the fd, so a long-running monitor eventually hit the
  // open-file limit (refs #5300). Keyed, and never evicted, so that probing a
  // second encoder type (the auto encoder list interleaves VAAPI and QSV)
  // cannot drop the context an earlier probe created. Owned here; VideoStore
  // borrows a reference through GetEncoderHWDeviceContext.
  std::map<std::pair<int, std::string>, AVBufferRef *> encoder_hw_device_ctxs;
  std::mutex      encoder_hw_device_ctx_mutex;
  std::string     output_container;
  _AVPIXELFORMAT  imagePixFormat;
  bool            record_audio;      // Whether to store the audio that we receive
  bool            audio_detection;   // Whether to score on how loud the audio is
  int             audio_threshold;   // 0-100; 0 means detection is off
  int             audio_alarm_score; // Score contributed while over threshold
  AudioDetector   audio_detector;
  bool            wallclock_timestamps; // Whether to use wallclock pts/dts instead of values from ffmpeg
  int             output_source_stream;


  int        brightness;        // The statically saved brightness of the camera
  int        contrast;        // The statically saved contrast of the camera
  int        hue;          // The statically saved hue of the camera
  int        colour;          // The statically saved colour of the camera

  std::string     event_prefix;    // The prefix applied to event names as they are created
  std::string     label_format;    // The format of the timestamp on the images
  Vector2      label_coord;      // The coordinates of the timestamp on the images
  int        label_size;         // Size of the timestamp on the images
  int32_t    image_buffer_count;        // Size of circular image buffer, kept in /dev/shm
  int32_t    max_image_buffer_count;    // Max # of video packets to keep in packet queue
  int        warmup_count;              // How many images to process before looking for events
  int        pre_event_count;    // How many images to hold and prepend to an alarm event
  int        post_event_count;    // How many unalarmed images must occur before the alarm state is reset
  int        stream_replay_buffer;   // How many frames to store to support DVR functions, IGNORED from this object, passed directly into zms now
  Seconds    section_length;      // How long events should last in continuous modes
  bool        section_length_warn;  // Whether to log a warning when a motion event exceeds desired section_length
  Seconds    min_section_length;   // Minimum event length when using event_close_mode == ALARM
  bool       startstop_on_section_length; // Whether to start/stop events on time % section_length
  bool       adaptive_skip;        // Whether to use the newer adaptive algorithm for this monitor
  int        motion_frame_skip;      // How many frames to skip in motion detection
  double     analysis_fps_limit;     // Target framerate for video analysis
  Microseconds analysis_update_delay;  //  How long we wait before updating analysis parameters
  Microseconds capture_delay;      // How long we wait between capture frames
  Microseconds alarm_capture_delay;  // How long we wait between capture frames when in alarm state
  int        alarm_frame_count;    // How many alarm frames are required before an event is triggered
  int        alert_to_alarm_frame_count;    // How many alarm frames (consecutive score frames) are required to return alarm from alert
  // value for now is the same number configured in alarm_frame_count, maybe getting his own parameter some day
  int        fps_report_interval;  // How many images should be captured/processed between reporting the current FPS
  int        ref_blend_perc;      // Percentage of new image going into reference image.
  int        alarm_ref_blend_perc;      // Percentage of new image going into reference image during alarm.
  bool       track_motion;      // Whether this monitor tries to track detected motion
  int         signal_check_points;  // Number of points in the image to check for signal
  Rgb         signal_check_colour;  // The colour that the camera will emit when no video signal detected
  bool        embed_exif; // Whether to embed Exif data into each image frame or not
  double      latitude;
  double      longitude;
  bool        rtsp_server; // Whether to include this monitor as an rtsp server stream
  std::string rtsp_streamname;      // path in the rtsp url for this monitor
  bool        soap_wsa_compl; // Whether the camera supports soap_wsa or not.
  std::string onvif_alarm_txt;     // def onvif_alarm_txt
  int         importance;           // Importance of this monitor, affects Connection logging errors.
  int         startup_delay;        // Seconds to sleep before connecting to camera
  unsigned int         zone_count;

  int capture_max_fps;

  Purpose      purpose;        // What this monitor has been created to do
  unsigned int  last_camera_bytes;

  int        event_count;
  int        last_capture_image_count; // last value of image_count when calculating capture fps
  int        analysis_image_count;    // How many frames have been processed by analysis thread.
  // Written by the decoder / analysis thread, read by the others: atomic.
  std::atomic<int> decoding_image_count;    // How many frames the decoder has written to shm.
  std::atomic<int> motion_frame_count;      // How many frames have had motion detection performed on them.
  int         last_motion_frame_count; // last value of motion_frame_count when calculating fps
  int        ready_count;
  int        first_alarm_count;
  int        last_alarm_count;
  bool       last_signal;
  // Capture assumes signal on the first packet after connect() cleared it,
  // even when the decoder measures it, so Analyse doesn't see a signal loss
  // before the first decoded frame. Capture thread only.
  bool       assume_signal_on_next_packet;
  int        buffer_count;
  State      state;
  SystemTimePoint start_time;
  SystemTimePoint last_fps_time;
  // How often each monitor writes its row of Monitor_Status, and the width of
  // the window the writes are staggered across (see Monitor::connect).
  static constexpr Seconds kStatusUpdateInterval = Seconds(10);
  SystemTimePoint last_status_time;
  SystemTimePoint last_analysis_fps_time;
  SystemTimePoint auto_resume_time;
  unsigned int      last_motion_score;

  EventCloseMode  event_close_mode;

#if ZM_MEM_MAPPED
  int             map_fd;
  std::string     mem_file;
#else // ZM_MEM_MAPPED
  int             shm_id;
#endif // ZM_MEM_MAPPED
  off_t           mem_size;
  unsigned char   *mem_ptr;
  SharedData      *shared_data;
  TriggerData     *trigger_data;
  VideoStoreData  *video_store_data;
  int             *zone_scores;

  struct timeval *shared_timestamps;
  unsigned char *shared_images;
  std::vector<Image *> image_buffer;
  AVPixelFormat *image_pixelformats;
  // Per-slot cross-process format for the analysis image ring (one entry per
  // analysis_image_buffer slot), mirroring image_pixelformats for the capture
  // ring. Replaces the former single alarm_image_pixelformat.
  AVPixelFormat *analysis_image_pixelformats;
  size_t shm_slot_size;  // per-slot byte capacity, sized to RGBA upper bound

  int video_stream_id; // will be filled in PrimeCapture
  int audio_stream_id; // will be filled in PrimeCapture
  // Always-on media output; survives camera reconnects, freed in destructor
  std::unique_ptr<StreamSocket> stream_socket;
  // Current capture health for the stream socket snapshot: 0 = healthy, else
  // the last fault event code, with its message. Guarded by stream_event_mutex
  // because health events come from the capture thread while state_changed
  // events come from the analysis thread.
  uint16_t stream_health_code = 0;
  // Mirror of the analysis state for the snapshot, written by SetState() under
  // stream_event_mutex so the capture thread never reads `state` itself.
  State stream_snapshot_state = IDLE;
  std::string stream_health_message;
  std::mutex stream_event_mutex;
  // Rebuild and cache the stream socket snapshot from current state + health.
  void RefreshStreamSnapshot();

  std::shared_ptr<Camera> camera;
  Event       *event;
  std::mutex   event_mutex;
  Storage     *storage;

  VideoStore          *videoStore;
  PacketQueue      packetqueue;
  // Stream timestamps kept going forward across a jump back in the camera's
  // clock: an offset (microseconds) added to every packet's dts and pts, and
  // each stream's last dts with the offset applied and its last step.
  int64_t dts_offset_ = 0;
  std::map<int, int64_t> last_dts_;
  std::map<int, int64_t> last_dts_step_;
  // A step back of more than this in a stream's dts is a jump in the camera's
  // clock, not reordering jitter.
  static constexpr int64_t kDtsStepBack = 1000000;
  // Moves the packet onto the continuous timeline; returns the offset applied.
  int64_t KeepDtsContinuous(AVPacket *packet, AVRational time_base);
  std::unique_ptr<PollThread> Poller;
  std::list<ZMPacketLock> decoder_queue;
  // The decoder_queue entry a decoded frame with this pts came from; releases
  // the entries before it that the codec consumed without returning a frame.
  std::list<ZMPacketLock>::iterator MatchDecodedFrame(int64_t frame_pts);
  // How many packets decoder_queue may hold, waiting on the codec, before
  // Decode() gives up on it and flushes.
  static size_t DecoderQueueLimit(int max_keyframe_interval, const AVCodecContext *context);
  // Set when Decode() flushed a full decoder_queue: nothing goes to the codec
  // until a keyframe, as it has no reference frames to decode the rest against.
  bool decoder_needs_keyframe = false;
  // Whether a packet must be held back from the codec while it waits for a
  // keyframe; clears the wait once the keyframe comes.
  bool SkipUntilKeyframe(bool keyframe);
  packetqueue_iterator  *analysis_it;
  std::unique_ptr<AnalysisThread> analysis_thread;
  packetqueue_iterator  *decoder_it;
  std::unique_ptr<DecoderThread> decoder;
  SwsContext   *convert_context;
  std::thread  close_event_thread;
  // Guards close_event_thread itself. closeEvent() runs on the analysis
  // thread and Pause() on the capture thread, and both join and reassign
  // it, so the object needs a lock of its own. Not the event lock: that is
  // held across closeEvent(), which spawns the thread this guards.
  std::mutex   close_event_thread_mutex;

  std::vector<Zone> zones;

#if MOSQUITTOPP_FOUND
  bool                      mqtt_enabled;
  std::vector<std::string>  mqtt_subscriptions;
  std::unique_ptr<MQTT> mqtt;
#endif

  const unsigned char  *privacy_bitmask;

  std::string linked_monitors_string;

  int      n_linked_monitors;
  MonitorLinkExpression *linked_monitors;
  //MonitorLink    **linked_monitors;
  std::string   event_start_command;
  std::string   event_end_command;
  std::vector<EventAction> actions;
  // Whether the Alarm actions have run for the alarm currently in progress.
  bool alarm_actions_fired;

  std::vector<Group *> groups;

  Image        delta_image;
  Image        ref_image;
  // ref_image is owned by the analysis thread (Analyse/DetectMotion). The
  // capture thread (CheckAction) must not touch its buffer directly; on
  // suspend-resume it sets this flag instead and the analysis thread drops the
  // stale reference itself on its next pass. Prevents a use-after-free/null
  // deref race in Image::Delta (refs #4983).
  std::atomic<bool> ref_image_reset_{false};
  // Set by CheckOndemand() when a linked monitor's alarm wakes this on-demand
  // monitor from its pause: steady_clock nanoseconds until which Analyse()
  // counts the link as alarmed, 0 for none. The link only reports an alarm while
  // it lasts, which can be shorter than reopening the camera takes, so the wake
  // has to be remembered until the first packet acts on it.
  std::atomic<int64_t> linked_wake_until_{0};
  // Analysis image ring: the annotated/analysis image is published into a ring
  // of image_buffer_count slots living in the alarm_images SHM region. Readers
  // pick up the newest via shared_data->last_analysis_index. Replaces the
  // former single alarm_image.
  std::vector<Image *> analysis_image_buffer;
  Image        write_image;    // Used when creating snapshot images
  std::string diag_path_ref;
  std::string diag_path_delta;

  RTSP2WebManager *RTSP2Web_Manager;
  Go2RTCManager *Go2RTC_Manager;
  JanusManager *Janus_Manager;
  AmcrestAPI *Amcrest_Manager;
  ONVIF *onvif;

  // Used in check signal
  uint8_t red_val;
  uint8_t green_val;
  uint8_t blue_val;
  uint8_t grayscale_val; /* 8bit grayscale color */
  uint8_t luma_val;      /* luma of the RGB colour, limited range */
  uint8_t luma_full_val; /* luma of the RGB colour, full range */
  Rgb colour_val; /* RGB32 color */
  int usedsubpixorder;

 public:
  explicit Monitor();

  ~Monitor();

  void AddPrivacyBitmask();

  void LoadCamera();
  const std::shared_ptr<Camera> getCamera() { return camera; }
  bool connect();
  bool disconnect();
  inline bool isConnected() const { return mem_ptr != nullptr; }

  inline int ShmValid() const {
    if (shared_data && shared_data->valid) {
      timeval now = {};
      gettimeofday(&now, nullptr);
      Debug(3, "Shared data is valid, checking heartbeat %" PRIi64 " - %" PRIi64 " = %" PRIi64"  < %f",
            static_cast<int64>(now.tv_sec),
            static_cast<int64>(shared_data->heartbeat_time),
            static_cast<int64>(now.tv_sec - shared_data->heartbeat_time),
            config.watch_max_delay);

      if ((now.tv_sec - shared_data->heartbeat_time) < config.watch_max_delay)
        return true;
    }
    return false;
  }
  inline unsigned int Id() const { return id; }
  inline const char *Name() const { return name.c_str(); }
  inline bool Deleted() const { return deleted; }
  inline unsigned int ServerId() const { return server_id; }
  inline Storage *getStorage() {
    if (!storage) {
      storage = new Storage(storage_id);
    }
    return storage;
  }
  inline CameraType GetType() const { return type; }

  CapturingOption Capturing() const { return capturing; }
  AnalysingOption Analysing() const { return analysing; }
  RecordingOption Recording() const { return recording; }

  inline PacketQueue * GetPacketQueue() { return &packetqueue; }

  // Called by the decoder thread as it exits. Releases packet locks for
  // anything it sent to the codec context but never received as a frame
  // (codec context is about to be torn down for Pause/reconnect, so those
  // packets will never produce output). Marks them decoded so the analysis
  // thread can advance past them.
  void flushDecoderQueue();
  inline bool Enabled() const {
    return shared_data->capturing;
  }
  DecodingOption Decoding() const {
    return decoding;
  }
  const std::string &DecoderName() const { return decoder_name; }
  const std::string &DecoderHWAccelName() const { return decoder_hwaccel_name; }
  const std::string &DecoderHWAccelDevice() const { return decoder_hwaccel_device; }
  bool JanusEnabled() {
    return janus_enabled;
  }
  bool JanusAudioEnabled() {
    return janus_audio_enabled;
  }
  inline const char* get_stream_key() {
    return shared_data->janus_pin;
  }

  bool has_out_of_order_packets() { return packetqueue.has_out_of_order_packets(); };
  int get_max_keyframe_interval() { return packetqueue.get_max_keyframe_interval(); };

  bool OnvifEnabled() {
    return onvif_event_listener;
  }
  int check_janus() {
    if (Janus_Manager) return Janus_Manager->check_janus();
    return -1;
  }
  bool EventPollerHealthy() const {
    if (onvif) {
      return onvif->isHealthy();
    } else if (Amcrest_Manager) {
      return Amcrest_Manager->isHealthy();
    }
    return false;
  }
  inline const char *EventPrefix() const { return event_prefix.c_str(); }
  inline bool Ready() const {
    if (!packetqueue.get_max_keyframe_interval()) {
      Debug(4, "Not ready because no keyframe interval.");
      return false;
    }
    if (decoding_image_count > ready_count) {
      Debug(4, "Ready because decoding_image_count(%d) > ready_count(%d)", decoding_image_count.load(), ready_count);
      return true;
    }
    Debug(4, "Not ready because decoding_image_count(%d) <= ready_count(%d)", decoding_image_count.load(), ready_count);
    return false;
  }
  inline bool Active() const {
    return shared_data->analysing;
  }
  int64_t getLastViewed() {
    if (shared_data && shared_data->valid)
      return shared_data->last_viewed_time;
    return 0;
  }
  void setLastViewed() {
    setLastViewed(std::chrono::system_clock::now());
  }
  void setLastViewed(SystemTimePoint new_time) {
    if (shared_data && shared_data->valid)
      shared_data->last_viewed_time =
        static_cast<int64>(std::chrono::duration_cast<Seconds>(new_time.time_since_epoch()).count());
  }
  // What zmc's capture loop does with an on-demand monitor on this pass.
  enum class OndemandStep { CAPTURE, PLAY, PAUSE, SLEEP };
  // viewed: watched in the last 10 s. has_image: an image has been written, so
  // thumbnails have something to show. primed: the camera is open. busy:
  // alarmed, in its post-event frames, or woken by a link that Analyse() hasn't
  // acted on yet. link_alarmed is asked only while the camera is closed, when
  // the analysis thread that otherwise reads the links is stopped.
  static OndemandStep DecideOndemand(bool viewed, bool has_image, bool primed, bool busy,
                                     const std::function<bool()> &link_alarmed);
  OndemandStep CheckOndemand();
  bool LinkedWakePending() const;
  // How long a link wake waits for the camera to reopen and a packet to act on it.
  static constexpr std::chrono::seconds kLinkedWakeTimeout{30};

  bool hasViewers() {
    if (shared_data && shared_data->valid) {
      SystemTimePoint now = std::chrono::system_clock::now();
      int64 intNow = static_cast<int64>(std::chrono::duration_cast<Seconds>(now.time_since_epoch()).count());
      Debug(3, "Last viewed %" PRId64 " seconds ago", intNow - shared_data->last_viewed_time);
      return (((!shared_data->last_viewed_time) or ((intNow - shared_data->last_viewed_time)) > 10)) ? false : true;
    }
    return false;
  }
  int64_t getLastAnalysisViewed() {
    if (shared_data && shared_data->valid)
      return shared_data->last_analysis_viewed_time;
    return 0;
  }
  void setLastAnalysisViewed() {
    setLastAnalysisViewed(std::chrono::system_clock::now());
  }
  void setLastAnalysisViewed(SystemTimePoint new_time) {
    if (shared_data && shared_data->valid)
      shared_data->last_analysis_viewed_time =
        static_cast<int64>(std::chrono::duration_cast<Seconds>(new_time.time_since_epoch()).count());
  }
  bool hasAnalysisViewers() {
    if (shared_data && shared_data->valid) {
      SystemTimePoint now = std::chrono::system_clock::now();
      int64 intNow = static_cast<int64>(std::chrono::duration_cast<Seconds>(now.time_since_epoch()).count());
      Debug(3, "Last analysis viewed %" PRId64 " seconds ago", intNow - shared_data->last_analysis_viewed_time);
      return (((!shared_data->last_analysis_viewed_time) or ((intNow - shared_data->last_analysis_viewed_time)) > 10)) ? false : true;
    }
    return false;
  }
  inline bool Exif() const { return embed_exif; }
  inline double Latitude() const { return shared_data ? shared_data->latitude : latitude; }
  inline double Longitude() const { return shared_data ? shared_data->longitude : longitude; }
  inline bool RTSPServer() const { return rtsp_server; }
  inline bool RecordAudio() const { return record_audio; }
  inline bool WallClockTimestamps() const { return wallclock_timestamps; }

  /*
  inline Purpose Purpose() { return purpose };
  inline Purpose Purpose( Purpose p ) { purpose = p; };
  */

  Orientation getOrientation() const;

  unsigned int Width() const { return width; }
  unsigned int Height() const { return height; }
  unsigned int Colours() const;
  unsigned int SubpixelOrder() const;

  int GetAudioFrequency() const { return shared_data ? shared_data->audio_frequency : -1; }
  int GetAudioChannels() const { return shared_data ? shared_data->audio_channels : -1; }

  int GetOptSaveJPEGs() const { return savejpegs; }
  VideoWriter GetOptVideoWriter() const { return videowriter; }
  const std::string &GetEncoderOptions() const { return encoderparams; }
  const std::string &EncoderHWAccelName() const { return encoder_hwaccel_name; }
  const std::string &EncoderHWAccelDevice() const { return encoder_hwaccel_device; }
#if HAVE_LIBAVUTIL_HWCONTEXT_H && LIBAVCODEC_VERSION_CHECK(57, 107, 0, 107, 0)
  // Return a new reference to the encoder hardware device context for (type,
  // device), creating and caching it on first use so every event's VideoStore
  // reuses one device instead of opening the render node again (refs #5300).
  // The caller owns the returned reference and must unref it. Returns nullptr
  // if the device cannot be created.
  AVBufferRef *GetEncoderHWDeviceContext(AVHWDeviceType type, const std::string &device);
#endif
  const std::string &OutputCodec() const { return output_codec; }
  const std::string &Encoder() const { return encoder; }
  const std::string &OutputContainer() const { return output_container; }

  uint64_t GetVideoWriterEventId() const { return video_store_data->current_event; }
  void SetVideoWriterEventId( uint64_t p_event_id ) { video_store_data->current_event = p_event_id; }

  SystemTimePoint GetVideoWriterStartTime() const {
    return SystemTimePoint(zm::chrono::duration_cast<Microseconds>(video_store_data->recording));
  }
  void SetVideoWriterStartTime(SystemTimePoint t) {
    video_store_data->recording = zm::chrono::duration_cast<timeval>(t.time_since_epoch());
  }

  unsigned int GetPreEventCount() const { return pre_event_count; };
  Seconds GetSectionLength() const { return section_length; };
  int32_t GetImageBufferCount() const { return image_buffer_count; };
  State GetState() const { return (State)shared_data->state.load(); }
  // Set the analysis state, publishing the transition as a stream socket
  // state_changed event (and refreshing the snapshot) when it actually changes.
  void SetState(State new_state);
  // Start the per-monitor media stream socket listener if it is not already
  // running. Independent of the camera: the socket carries lifecycle events
  // (capture faults) before any media, so zmc starts it before the first
  // connect attempt to make startup faults observable. PrimeCapture() calls it
  // too, then adds stream parameters. Producer side only (zmc).
  void StartStreamSocket();
  // Emit a capture-fault lifecycle event on the stream socket and update the
  // cached health snapshot. code is one of the kEvent* health codes; a *_failed
  // code sets the snapshot's active fault, a *_restored/_resumed code clears it.
  void SendStreamHealthEvent(uint16_t code, const std::string &message, int detail = 0);

  AVStream *GetAudioStream() const { return camera ? camera->getAudioStream() : nullptr; };
  AVStream *GetVideoStream() const { return camera ? camera->getVideoStream() : nullptr; };
  AVCodecContext *GetVideoCodecContext() const { return mVideoCodecContext; };

  std::string GetSecondPath() const { return second_path; };
  std::string GetStreamSocketPath() const { return shared_data ? shared_data->stream_socket_path : ""; };
  std::string GetRTSPStreamName() const { return rtsp_streamname; };

  const std::string &getONVIF_URL() const { return onvif_url; };
  const std::string &getONVIF_Username() const { return onvif_username; };
  const std::string &getONVIF_Password() const { return onvif_password; };
  const std::string &getONVIF_Options() const { return onvif_options; };

  Image *GetAlarmImage();
  // Writer-side helper: copies src into alarm_image and publishes its
  // AVPixelFormat so reader processes can correctly interpret the SHM bytes.
  void WriteAlarmImage(const Image &src);
  int GetImage(int32_t index=-1, int scale=100);
  std::shared_ptr<ZMPacket> getSnapshot( int index=-1 );
  SystemTimePoint GetTimestamp(int index = -1);
  void UpdateAdaptiveSkip();
  useconds_t GetAnalysisRate();
  Microseconds GetAnalysisUpdateDelay() const { return analysis_update_delay; }
  unsigned int GetCaptureMaxFPS() const { return capture_max_fps; }
  Microseconds GetCaptureDelay() const { return capture_delay; }
  Microseconds GetAlarmCaptureDelay() const { return alarm_capture_delay; }
  int GetLastReadIndex() const;
  int GetLastWriteIndex() const;
  uint64_t GetLastEventId() const;
  double GetFPS() const;
  void UpdateFPS();
  void ForceAlarmOn( int force_score, const char *force_case, const char *force_text="" );
  void ForceAlarmOff();
  void CancelForced();
  TriggerState GetTriggerState() const { return trigger_data ? trigger_data->trigger_state : TRIGGER_CANCEL; }
  // shared_data is null until connect() maps the shm segment, and connect()
  // fails with it still null whenever the mmap file cannot be opened, grown or
  // mapped -- wrong ownership, or /dev/shm too small for the requested buffers.
  // zmc's startup loop calls SetHeartbeatTime() on every failed retry, so
  // without these guards a monitor that cannot get its shm takes zmc down with
  // SIGSEGV instead of retrying. The accessors around these already guard the
  // same way.
  SystemTimePoint GetStartupTime() const {
    if (!shared_data) return SystemTimePoint();
    return std::chrono::system_clock::from_time_t(shared_data->startup_time);
  }
  void SetStartupTime(SystemTimePoint time) {
    if (!shared_data) return;
    shared_data->startup_time = std::chrono::system_clock::to_time_t(time);
  }
  void SetHeartbeatTime(SystemTimePoint time) {
    if (!shared_data) return;
    shared_data->heartbeat_time = std::chrono::system_clock::to_time_t(time);
  }
  void get_ref_image();

  int LabelSize() const { return label_size; }

  void actionReload();
  void actionEnable();
  void actionDisable();
  void actionSuspend();
  void actionResume();

  int actionBrightness(int p_brightness);
  int actionBrightness();
  int actionHue(int p_hue);
  int actionHue();
  int actionColour(int p_colour);
  int actionColour();
  int actionContrast(int p_contrast);
  int actionContrast();

  int PrimeCapture();
  // Open a decoder for the camera's video stream, and tear it down. Safe to
  // call when there is nothing to decode: cameras that hand over images rather
  // than packets have no video stream.
  int OpenDecoder();
  void CloseDecoder();
  int PreCapture() const;
  int Capture();
  int PostCapture() const;
  int Pause();
  int Play();
  int Close();

  void CheckAction();

  unsigned int DetectMotion( const Image &comp_image, Event::StringSet &zoneSet );
  unsigned int AnalyseFrame( const Image &frame_image, Event::StringSet &zoneSet );
  unsigned int AnalyseFrame( const Image &frame_image, Event::StringSet &zoneSet, Image *analysis_image );
  // DetectBlack seems to be unused. Check it on zm_monitor.cpp for more info.
  //unsigned int DetectBlack( const Image &comp_image, Event::StringSet &zoneSet );
  bool CheckSignal( const Image *image );
  // True when the decoder measures shared_data->signal (SignalCheckPoints)
  // for the packets it decodes; Capture then leaves the flag to it, so the
  // two threads don't overwrite each other's value. Pure, for tests.
  static bool DecoderChecksSignal(DecodingOption decoding, int signal_check_points, bool has_viewers);
  bool Analyse();
  bool setupConvertContext(const AVFrame *input_frame, const Image *image);
  // Write capture_image into image_buffer[index] without conversion and
  // record its AVPixelFormat in image_pixelformats[index] so reading
  // processes can adopt that format via ReadShmFrame.
  void WriteShmFrame(unsigned int index, Image *capture_image);

  // Read-side counterpart: ensures image_buffer[index]'s metadata matches
  // the format zmc wrote via image_pixelformats[index] before returning
  // it. Use this from zms / zma / event paths instead of touching
  // image_buffer[index] directly.
  Image *ReadShmFrame(unsigned int index);
  void applyOrientation(Image *image);
  bool applyDeinterlacing(std::shared_ptr<ZMPacket> &packet, Image *capture_image);
  bool Decode();
  bool Poll();
  void DumpImage( Image *dump_image ) const;
  std::string Substitute(const std::string &format, SystemTimePoint ts_time) const;
  void TimestampImage(Image *ts_image, SystemTimePoint ts_time) const;
  Event *openEvent(
    ZMPacketLock *packet_lock,
    const std::string &cause,
    const Event::StringSetMap &noteSetMap);
  // Join a close that is still in flight, so whatever it holds -- notably a
  // hardware encoder session belonging to the previous event -- is released.
  // Returns true if there was one to wait for.
  bool WaitForEventClose();
  void closeEvent();

  void Reload();
  void ReloadZones();
  void ReloadLinkedMonitors();

  void LoadActions();
  // Fires the AlarmEnd actions once per alarm, and only when the Alarm actions
  // actually ran, so the two stay paired.
  void EndAlarmActions();
  void RunActions(EventAction::TriggerOn trigger);
  // Pure helpers, separated from RunActions so they can be tested without a
  // database, a socket or a device. ActionCommandName maps the DB enum onto a
  // zmcontrol method rather than passing the stored string through, so nothing
  // from the database is interpolated into the message uninspected.
  // Builds the Ffmpeg-equivalent URL for a Remote/rtsp monitor, so the
  // deprecation warning can name the exact Source Path to switch to. Pure, so
  // it is testable without a camera; pass the result through
  // remove_authentication() before logging it.
  static std::string RtspUrlFromRemote(const std::string &host, const std::string &port,
                                       const std::string &path, const std::string &user,
                                       const std::string &pass);
  static const char *ActionCommandName(const std::string &action_type);
  static std::string ActionMessage(const EventAction &action);
  static const char *ActionTriggerName(EventAction::TriggerOn trigger);

  bool DumpSettings( char *output, bool verbose );
  void DumpZoneImage( const char *zone_string=0 );
  std::vector<Group *>  Groups();
  StringVector GroupNames();

  static std::vector<std::shared_ptr<Monitor>> LoadMonitors(const std::string &sql, Purpose purpose);  // Returns # of Monitors loaded, 0 on failure.
#if ZM_HAS_V4L2
  static std::vector<std::shared_ptr<Monitor>> LoadLocalMonitors(const char *device, Purpose purpose);
#endif // ZM_HAS_V4L2
  static std::vector<std::shared_ptr<Monitor>> LoadRemoteMonitors(const char *protocol, const char *host, const char*port, const char*path, Purpose purpose);
  static std::vector<std::shared_ptr<Monitor>> LoadFileMonitors(const char *file, Purpose purpose);
  static std::vector<std::shared_ptr<Monitor>> LoadFfmpegMonitors(const char *file, Purpose purpose);
  static std::shared_ptr<Monitor> Load(unsigned int id, bool load_zones, Purpose purpose);
  void Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose);
  //void writeStreamImage( Image *image, struct timeval *timestamp, int scale, int mag, int x, int y );
  //void StreamImages( int scale=100, int maxfps=10, time_t ttl=0, int msq_id=0 );
  //void StreamImagesRaw( int scale=100, int maxfps=10, time_t ttl=0 );
  //void StreamImagesZip( int scale=100, int maxfps=10, time_t ttl=0 );
  //void StreamMpeg( const char *format, int scale=100, int maxfps=10, int bitrate=100000 );
  double get_capture_fps( ) const {
    return shared_data ? shared_data->capture_fps : 0.0;
  }
  double get_analysis_fps( ) const {
    return shared_data ? shared_data->analysis_fps : 0.0;
  }
  int Importance() const { return importance; }
  int StartupDelay() const { return startup_delay; }

  // Whether anything wants the audio level right now: either the monitor
  // scores on it, or the editor's meter has asked for a reading and its
  // request has not expired. Decoding audio is not free, so a monitor nobody
  // is asking about does not do it.
  bool AudioLevelWanted(SystemTimePoint now) const;

  // Peak audio level since the last call, for the Frames row about to be
  // written. Clears on read, so each row covers its own interval. Always 0
  // when the monitor has AudioDetection off, because nothing runs the
  // detector then, and that is what tells the event view there is no audio
  // series to draw.
  int TakeAudioPeak() { return audio_detector.TakePeak(); }

 private:
  // True after a keyframe is sent until the decoder outputs the first frame.
  // Used by keyframe-based decoding modes to feed any required follow-up packets.
  // A future improvement could eliminate mode-specific checks by relying solely
  // on this flag to track the decoder state.
  bool decoder_requires_next_packet = false;
};

#define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit))

#endif // ZM_MONITOR_H