src/zm_stream_socket_protocol.h
Rama master · réplica de lectura del repositorio original.
/*
* This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#ifndef ZM_STREAM_SOCKET_PROTOCOL_H
#define ZM_STREAM_SOCKET_PROTOCOL_H
#include <cstdint>
#include <string>
#include <vector>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavutil/rational.h>
}
// Wire protocol for the per-monitor media stream socket
// (PATH_SOCKS/stream_{monitor_id}.sock). All integers little-endian.
//
// Every message:
// u32 length bytes following this field (fixed header remainder + payload)
// u8 version protocol version, currently 1
// u8 type MessageType
// u8 stream StreamId (0 video, 1 audio)
// u8 flags bit 0: keyframe (video); other bits reserved, must be 0
// u32 sequence per-stream, counts every message produced (drops appear as gaps)
// u32 generation stream epoch; a bump means re-init the decoder from a new HELLO
// i64 pts_us signed microseconds (two's-complement), AV_TIME_BASE_Q,
// shared clock for both streams; AV_NOPTS_VALUE = unknown
// [payload]
//
// HELLO payload is a TLV list (u8 tag, u16 length, value); unknown tags must
// be skipped by consumers. HELLOs are sent per stream on connect and on every
// generation bump, audio before video: the video HELLO is always the last
// HELLO of a generation, so a consumer that sees it has that generation's
// complete parameter set (a stream not re-announced by then is gone).
namespace zm {
namespace stream_socket {
constexpr uint8_t kProtocolVersion = 1;
// Total serialized header size, including the leading length field.
constexpr size_t kHeaderSize = 24;
// Bytes of fixed header counted by the length field (kHeaderSize minus the
// length field itself).
constexpr uint32_t kHeaderLengthBytes = kHeaderSize - sizeof(uint32_t);
// Sanity cap on the length field; larger values mean a corrupt or hostile peer.
constexpr uint32_t kMaxMessageLength = 32 * 1024 * 1024;
// Largest value a TLV can carry (u16 length field). Longer values are
// omitted (extradata) or clamped (strings) by the builders.
constexpr size_t kMaxTlvValueSize = 0xffff;
enum class MessageType : uint8_t {
Hello = 0x01,
Media = 0x02,
Keyframe = 0x03,
Stats = 0x04,
Bye = 0x05,
Event = 0x06,
};
enum class StreamId : uint8_t {
Video = 0,
Audio = 1,
Monitor = 2, // EVENT frames: monitor lifecycle, neither video nor audio
};
constexpr uint8_t kFlagKeyframe = 0x01;
// HELLO TLV tags
constexpr uint8_t kTlvCodecId = 0x01; // u32, AVCodecID value
constexpr uint8_t kTlvExtradata = 0x02; // raw codecpar->extradata
constexpr uint8_t kTlvWidth = 0x03; // u32
constexpr uint8_t kTlvHeight = 0x04; // u32
constexpr uint8_t kTlvFpsNum = 0x05; // u32
constexpr uint8_t kTlvFpsDen = 0x06; // u32
constexpr uint8_t kTlvSampleRate = 0x07; // u32
constexpr uint8_t kTlvChannels = 0x08; // u32
constexpr uint8_t kTlvProfile = 0x09; // u32
constexpr uint8_t kTlvLevel = 0x0A; // u32
// utf8, the FFmpeg codec name (avcodec_get_name: "h264", "hevc", "aac"...).
// AVCodecID values are renumbered between FFmpeg major versions (HEVC is 173
// in libavcodec 61 and 172 in libavcodec 63), so a consumer linked against a
// different FFmpeg than zmc must identify the codec by name. ParseHello
// prefers it.
constexpr uint8_t kTlvCodecName = 0x0B;
struct Header {
uint32_t length; // bytes following the length field == kHeaderLengthBytes + payload size
uint8_t version;
uint8_t type;
uint8_t stream;
uint8_t flags;
uint32_t sequence;
uint32_t generation;
int64_t pts_us; // signed; AV_NOPTS_VALUE means unknown
uint32_t payload_size() const { return length - kHeaderLengthBytes; }
};
void SerializeHeader(const Header &header, uint8_t out[kHeaderSize]);
// Returns false if the version is unsupported or the length field is
// impossible (shorter than the fixed header remainder or above the cap).
bool ParseHeader(const uint8_t in[kHeaderSize], Header &header);
// Decoded HELLO parameters. Zero means "not present on the wire" for every
// field except codec_id, which is required. codec_id is resolved from
// codec_name when this FFmpeg knows the name, and from the numeric id
// otherwise.
struct HelloInfo {
AVCodecID codec_id = AV_CODEC_ID_NONE;
std::string codec_name; // as sent; empty from a zmc that predates the tag
std::vector<uint8_t> extradata;
uint32_t width = 0;
uint32_t height = 0;
uint32_t fps_num = 0;
uint32_t fps_den = 0;
uint32_t sample_rate = 0;
uint32_t channels = 0;
uint32_t profile = 0;
uint32_t level = 0;
};
// Builds the HELLO payload TLV list from codec parameters. frame_rate is only
// used for video parameters; pass {0, 0} when unknown.
std::vector<uint8_t> BuildHello(const AVCodecParameters *par, AVRational frame_rate);
// Parses a HELLO payload, skipping unknown tags. Returns false on a truncated
// TLV, or when neither the codec name nor the codec id identifies a codec.
bool ParseHello(const uint8_t *data, size_t len, HelloInfo &info);
// STATS payload: u64 messages_sent, u64 messages_dropped_for_this_client.
std::vector<uint8_t> BuildStats(uint64_t sent, uint64_t dropped);
bool ParseStats(const uint8_t *data, size_t len, uint64_t &sent, uint64_t &dropped);
// EVENT frame (MessageType::Event, StreamId::Monitor). The payload is a fixed
// u16 event_code followed by a TLV tail (u8 tag, u16 length, value); unknown
// tags are skipped, exactly like HELLO. Events are a monitor lifecycle channel,
// independent of media: the header generation correlates an event with the
// media epoch in effect, the header sequence counts every event produced so a
// slow consumer sees drops as gaps, and wall-clock time travels in a TLV.
constexpr uint16_t kEventSnapshot = 0x0001; // current health+state, on connect
constexpr uint16_t kEventConnectionFailed = 0x0101;
constexpr uint16_t kEventConnectionRestored = 0x0102;
constexpr uint16_t kEventPrimeCaptureFailed = 0x0103;
constexpr uint16_t kEventPrimeCaptureRestored = 0x0104;
constexpr uint16_t kEventCaptureFailed = 0x0105;
constexpr uint16_t kEventCaptureResumed = 0x0106;
constexpr uint16_t kEventStateChanged = 0x0201;
// EVENT TLV tags
constexpr uint8_t kTlvWallClockUs = 0x01; // u64, unix-epoch microseconds
constexpr uint8_t kTlvMessage = 0x02; // utf8, human-readable detail
constexpr uint8_t kTlvStateId = 0x03; // u32, current monitor state
constexpr uint8_t kTlvPrevStateId = 0x04; // u32, previous state (state_changed)
constexpr uint8_t kTlvDetail = 0x05; // u32, errno / ffmpeg error code
constexpr uint8_t kTlvStateName = 0x06; // utf8, "Idle"/"Alarm"/...
constexpr uint8_t kTlvHealthCode = 0x07; // u16, active fault code in a snapshot (0 = healthy)
// Decoded EVENT payload. A field is "present" only when its has_* flag is set;
// the wire omits fields that do not apply to a given event_code.
struct MonitorEvent {
uint16_t code = 0;
uint64_t wall_clock_us = 0; bool has_wall_clock = false;
std::string message;
uint32_t state_id = 0; bool has_state_id = false;
uint32_t prev_state_id = 0; bool has_prev_state_id = false;
uint32_t detail = 0; bool has_detail = false;
std::string state_name;
uint16_t health_code = 0; bool has_health_code = false; // snapshot: active fault
};
// Builds an EVENT payload (u16 code + TLV tail) from the populated fields of ev.
// Only fields whose has_* flag is set (or whose string is non-empty) are
// emitted. Header framing (sequence/generation/pts) is added by the caller.
std::vector<uint8_t> BuildEvent(const MonitorEvent &ev);
// Parses an EVENT payload, skipping unknown tags. Returns false on a truncated
// fixed code or TLV.
bool ParseEvent(const uint8_t *data, size_t len, MonitorEvent &ev);
} // namespace stream_socket
} // namespace zm
#endif // ZM_STREAM_SOCKET_PROTOCOL_H