From 37fda1a8dcb39b1df180cf38cd5f2bad99aeb423 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 31 Jan 2026 16:19:38 -0500 Subject: [PATCH] Add zm_rtsp_server_fifo_av1_source --- src/zm_rtsp_server_fifo_av1_source.cpp | 215 +++++++++++++++++++++++++ src/zm_rtsp_server_fifo_av1_source.h | 76 +++++++++ 2 files changed, 291 insertions(+) create mode 100644 src/zm_rtsp_server_fifo_av1_source.cpp create mode 100644 src/zm_rtsp_server_fifo_av1_source.h diff --git a/src/zm_rtsp_server_fifo_av1_source.cpp b/src/zm_rtsp_server_fifo_av1_source.cpp new file mode 100644 index 000000000..506d7d18f --- /dev/null +++ b/src/zm_rtsp_server_fifo_av1_source.cpp @@ -0,0 +1,215 @@ +/* --------------------------------------------------------------------------- +** +** zm_rtsp_server_fifo_av1_source.cpp +** +** AV1 ZoneMinder RTSP source +** +** -------------------------------------------------------------------------*/ + +#include "zm_rtsp_server_fifo_av1_source.h" + +#include "zm_config.h" +#include "zm_logger.h" +#include "xop/AV1Source.h" + +#if HAVE_RTSP_SERVER + +AV1_ZoneMinderFifoSource::AV1_ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + const std::string &fifo +) + : ZoneMinderFifoVideoSource(rtspServer, sessionId, channelId, fifo) { + m_hType = 1; // AV1 identifier +} + +// Parse LEB128 variable-length integer (used for OBU sizes in AV1) +size_t AV1_ZoneMinderFifoSource::parseLEB128( + unsigned char* data, size_t max_size, uint32_t& value) { + value = 0; + size_t bytes_read = 0; + + for (size_t i = 0; i < max_size && i < 8; i++) { + uint8_t byte = data[i]; + value |= ((byte & 0x7F) << (7 * i)); + bytes_read++; + + if ((byte & 0x80) == 0) { + // Last byte of LEB128 + break; + } + } + + return bytes_read; +} + +// Parse OBU header +// Returns header size (1 or 2 bytes), 0 on error +size_t AV1_ZoneMinderFifoSource::parseOBUHeader( + unsigned char* data, size_t size, uint8_t& obu_type, bool& has_size) { + if (size < 1) { + return 0; + } + + // OBU header byte: + // - obu_forbidden_bit (1 bit) - must be 0 + // - obu_type (4 bits) + // - obu_extension_flag (1 bit) + // - obu_has_size_field (1 bit) + // - obu_reserved_1bit (1 bit) + + uint8_t header = data[0]; + + if (header & 0x80) { + // Forbidden bit is set - invalid + Debug(1, "AV1: OBU forbidden bit set"); + return 0; + } + + obu_type = (header >> 3) & 0x0F; + bool has_extension = (header >> 2) & 0x01; + has_size = (header >> 1) & 0x01; + + size_t header_size = 1; + + if (has_extension) { + if (size < 2) { + return 0; + } + // Extension byte contains temporal_id and spatial_id + header_size = 2; + } + + return header_size; +} + +// Extract a single OBU from the buffer +unsigned char* AV1_ZoneMinderFifoSource::extractFrame( + unsigned char* frame, size_t& size, size_t& outsize) { + outsize = 0; + + if (size < 1) { + return nullptr; + } + + uint8_t obu_type; + bool has_size; + + size_t header_size = parseOBUHeader(frame, size, obu_type, has_size); + if (header_size == 0) { + Debug(1, "AV1: Failed to parse OBU header"); + return nullptr; + } + + size_t obu_size; + + if (has_size) { + // Parse LEB128 size field after header + if (size < header_size + 1) { + return nullptr; + } + + uint32_t leb_value; + size_t leb_size = parseLEB128(frame + header_size, size - header_size, leb_value); + if (leb_size == 0) { + Debug(1, "AV1: Failed to parse LEB128 size"); + return nullptr; + } + + obu_size = header_size + leb_size + leb_value; + } else { + // No size field - OBU extends to end of buffer + // This is typically only used for the last OBU in a temporal unit + obu_size = size; + } + + if (obu_size > size) { + Debug(1, "AV1: OBU size %zu exceeds buffer size %zu", obu_size, size); + return nullptr; + } + + outsize = obu_size; + size -= obu_size; + + Debug(4, "AV1: Extracted OBU type=%u size=%zu remaining=%zu", obu_type, outsize, size); + + return frame; +} + +// Split buffer into individual OBUs +std::list> +AV1_ZoneMinderFifoSource::splitFrames(unsigned char* frame, size_t &frameSize) { + std::list> frameList; + + size_t bufSize = frameSize; + size_t size = 0; + unsigned char* buffer = this->extractFrame(frame, bufSize, size); + + while (buffer != nullptr && size > 0) { + // Parse OBU type from this OBU + uint8_t obu_type; + bool has_size; + size_t header_size = parseOBUHeader(buffer, size, obu_type, has_size); + + if (header_size > 0) { + switch (obu_type) { + case AV1_OBU_SEQUENCE_HEADER: + Debug(4, "AV1 Sequence Header: size=%zu", size); + m_sequenceHeader.assign((char*)buffer, size); + if (m_av1Source && !m_sequenceHeader.empty()) { + // Extract just the sequence header payload (skip OBU header and size) + size_t payload_offset = header_size; + if (has_size) { + uint32_t obu_payload_size; + size_t leb_size = parseLEB128(buffer + header_size, size - header_size, obu_payload_size); + payload_offset += leb_size; + } + if (payload_offset < size) { + m_av1Source->SetSequenceHeader( + (const uint8_t*)(buffer + payload_offset), + size - payload_offset); + Debug(2, "AV1: Set sequence header on xop source (%zu bytes)", + size - payload_offset); + } + } + break; + + case AV1_OBU_TEMPORAL_DELIMITER: + Debug(4, "AV1 Temporal Delimiter"); + break; + + case AV1_OBU_FRAME_HEADER: + Debug(4, "AV1 Frame Header: size=%zu", size); + break; + + case AV1_OBU_FRAME: + Debug(4, "AV1 Frame: size=%zu", size); + break; + + case AV1_OBU_TILE_GROUP: + Debug(4, "AV1 Tile Group: size=%zu", size); + break; + + case AV1_OBU_METADATA: + Debug(4, "AV1 Metadata: size=%zu", size); + break; + + default: + Debug(4, "AV1 OBU type=%u size=%zu", obu_type, size); + break; + } + } + + frameList.push_back(std::pair(buffer, size)); + + if (bufSize == 0) break; + + buffer = this->extractFrame(buffer + size, bufSize, size); + } + + frameSize = bufSize; + return frameList; +} + +#endif // HAVE_RTSP_SERVER diff --git a/src/zm_rtsp_server_fifo_av1_source.h b/src/zm_rtsp_server_fifo_av1_source.h new file mode 100644 index 000000000..dfede1337 --- /dev/null +++ b/src/zm_rtsp_server_fifo_av1_source.h @@ -0,0 +1,76 @@ +/* --------------------------------------------------------------------------- +** This software is in the public domain, furnished "as is", without technical +** support, and with no warranty, express or implied, as to its usefulness for +** any purpose. +** +** zm_rtsp_server_fifo_av1_source.h +** +** AV1 ZoneMinder RTSP source +** +** -------------------------------------------------------------------------*/ + +#ifndef ZM_RTSP_AV1_FIFO_SOURCE_H +#define ZM_RTSP_AV1_FIFO_SOURCE_H + +#include "zm_config.h" +#include "zm_rtsp_server_fifo_video_source.h" + +// Forward declaration +namespace xop { + class AV1Source; +} + +#if HAVE_RTSP_SERVER + +// AV1 OBU Types (from AV1 specification) +enum AV1_OBU_Type { + AV1_OBU_SEQUENCE_HEADER = 1, + AV1_OBU_TEMPORAL_DELIMITER = 2, + AV1_OBU_FRAME_HEADER = 3, + AV1_OBU_TILE_GROUP = 4, + AV1_OBU_METADATA = 5, + AV1_OBU_FRAME = 6, + AV1_OBU_REDUNDANT_FRAME_HEADER = 7, + AV1_OBU_TILE_LIST = 8, + AV1_OBU_PADDING = 15 +}; + +class AV1_ZoneMinderFifoSource : public ZoneMinderFifoVideoSource { + public: + AV1_ZoneMinderFifoSource( + std::shared_ptr& rtspServer, + xop::MediaSessionId sessionId, + xop::MediaChannelId channelId, + const std::string &fifo + ); + + virtual ~AV1_ZoneMinderFifoSource() {} + + // Override ZoneMinderFifoSource + virtual std::list> + splitFrames(unsigned char* frame, size_t &frameSize) override; + virtual unsigned char* extractFrame(unsigned char* frame, + size_t& size, size_t& outsize) override; + + void setAV1Source(xop::AV1Source* source) { m_av1Source = source; } + + protected: + // Parse OBU header and return header size + // obu_type: output - the OBU type + // has_size: output - whether OBU has size field + // Returns: header size in bytes, 0 on error + size_t parseOBUHeader(unsigned char* data, size_t size, + uint8_t& obu_type, bool& has_size); + + // Parse LEB128 variable-length integer + // value: output - the parsed value + // Returns: number of bytes consumed, 0 on error + size_t parseLEB128(unsigned char* data, size_t max_size, uint32_t& value); + + std::string m_sequenceHeader; + xop::AV1Source* m_av1Source = nullptr; +}; + +#endif // HAVE_RTSP_SERVER + +#endif // ZM_RTSP_AV1_FIFO_SOURCE_H