[bTSstream-devel] [Git][videolan/bitstream][master] SMPTE 2108-1 HDR/WCG Metadata Packing in VANC
Christophe Massiot (@cmassiot)
gitlab at videolan.org
Sat Jul 4 09:48:37 UTC 2026
Christophe Massiot pushed to branch master at VideoLAN / bitstream
Commits:
cc05d361 by Rafaël Carré at 2026-06-23T12:53:11+02:00
SMPTE 2108-1 HDR/WCG Metadata Packing in VANC
- - - - -
1 changed file:
- + smpte/2108.h
Changes:
=====================================
smpte/2108.h
=====================================
@@ -0,0 +1,447 @@
+/*****************************************************************************
+ * 2108.h: SMPTE ST 2108-1 HDR/WCG Metadata Packing in VANC
+ *****************************************************************************
+ * Copyright (C) 2026 Open Broadcast Systems Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject
+ * to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+
+/*
+ * Normative references:
+ * - SMPTE ST 2108-1:2018 HDR/WCG Metadata Packing and Signaling in the
+ * Vertical Ancillary Data Space
+ */
+
+#ifndef __BITSTREAM_SMPTE_2108_H__
+#define __BITSTREAM_SMPTE_2108_H__
+
+#include <stdint.h> /* uint8_t, uint16_t, etc... */
+#include <stdbool.h> /* bool */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/*****************************************************************************
+ * SMPTE ST 2108-1 HDR/WCG Metadata Ancillary Data Packet
+ *
+ * Carried as a SMPTE ST 291-1 Type 2 ANC packet in VANC with:
+ * DID = 0x41
+ * SDID = 0x0C
+ *
+ * The UDW payload consists of one or more HDR/WCG Metadata Frames,
+ * each with the structure:
+ *
+ * Byte 0 : HDR/WCG Metadata Frame Type (uint8)
+ * Byte 1 : HDR/WCG Metadata Frame Length j (uint8, number of data bytes)
+ * Bytes 2..j+1 : HDR/WCG Metadata Frame Data
+ *****************************************************************************/
+
+/* ANC packet identifiers (Table 1) */
+#define S2108_DID 0x41
+#define S2108_SDID 0x0C
+
+/* HDR/WCG Metadata Frame Type values (Table 3) */
+#define S2108_FRAME_TYPE_STATIC1 0x00 /* Static Metadata Type 1 –
+ Mastering Display Colour Volume
+ (ITU-T H.265 SEI type 137) */
+#define S2108_FRAME_TYPE_STATIC2 0x01 /* Static Metadata Type 2 –
+ Content Light Level
+ (ITU-T H.265 SEI type 144) */
+#define S2108_FRAME_TYPE_DYNAMIC1 0x02 /* Dynamic Metadata Type 1 –
+ ATSC A/341 ST 2094-10 */
+#define S2108_FRAME_TYPE_DYNAMIC5 0x06 /* Dynamic Metadata Type 5 –
+ ETSI TS 103 433-1 SL-HDR1 */
+
+/* Fixed frame lengths for the two static frame types (Table 2 constraints) */
+#define S2108_STATIC1_FRAME_LENGTH 0x1A /* 26 bytes */
+#define S2108_STATIC2_FRAME_LENGTH 0x06 /* 6 bytes */
+
+/* SEI payloadType values embedded in Static frame data */
+#define S2108_STATIC1_SEI_TYPE 0x89 /* 137 – mastering_display_colour_volume */
+#define S2108_STATIC1_SEI_SIZE 0x18 /* 24 bytes of SEI payload */
+#define S2108_STATIC2_SEI_TYPE 0x90 /* 144 – content_light_level_info */
+#define S2108_STATIC2_SEI_SIZE 0x04 /* 4 bytes of SEI payload */
+
+/* Minimum size of a metadata frame header (type + length fields) */
+#define S2108_FRAME_HEADER_SIZE 2
+
+/* Header bytes for Dynamic Metadata Type 1 (Table 4 / ANSI/SCTE 128-1) */
+#define S2108_DYNAMIC1_COUNTRY_CODE 0xB5 /* itu_t_t35_country_code (USA) */
+#define S2108_DYNAMIC1_PROVIDER_MSB 0x00 /* itu_t_t35_provider_code MSB (ATSC) */
+#define S2108_DYNAMIC1_PROVIDER_LSB 0x31 /* itu_t_t35_provider_code LSB (ATSC) */
+#define S2108_DYNAMIC1_USER_ID_0 0x47 /* user_identifier byte 0 */
+#define S2108_DYNAMIC1_USER_ID_1 0x41 /* user_identifier byte 1 */
+#define S2108_DYNAMIC1_USER_ID_2 0x39 /* user_identifier byte 2 */
+#define S2108_DYNAMIC1_USER_ID_3 0x34 /* user_identifier byte 3 */
+#define S2108_DYNAMIC1_TYPE_CODE 0x09 /* user_data_type_code for ST2094-10 */
+#define S2108_DYNAMIC1_MARKER_BITS 0xFF /* marker_bits concluding ATSC1_data() */
+
+/* Overhead bytes added around the raw ST2094-10 payload in a Dynamic Type 1 frame:
+ * SEI payloadType (1) + SEI payloadSize (1) + 7 SCTE header bytes +
+ * user_data_type_code (1) = 10 bytes before the data, then 1 marker byte after.
+ * Frame Length = 11 + sizeof(ST2094-10_data). */
+#define S2108_DYNAMIC1_OVERHEAD 11
+
+/*****************************************************************************
+ * HDR/WCG Metadata Frame accessors
+ *
+ * All functions operate on a pointer to the first byte of an HDR/WCG Metadata
+ * Frame within the UDW payload (i.e. after ST 291-1 ANC framing).
+ *****************************************************************************/
+
+/* --- Frame Type (byte 0) -------------------------------------------------- */
+
+static inline uint8_t s2108_get_frame_type(const uint8_t *p_frame)
+{
+ return p_frame[0];
+}
+
+static inline void s2108_set_frame_type(uint8_t *p_frame, uint8_t type)
+{
+ p_frame[0] = type;
+}
+
+/* --- Frame Length (byte 1) ------------------------------------------------ */
+
+static inline uint8_t s2108_get_frame_length(const uint8_t *p_frame)
+{
+ return p_frame[1];
+}
+
+static inline void s2108_set_frame_length(uint8_t *p_frame, uint8_t length)
+{
+ p_frame[1] = length;
+}
+
+/* --- Frame Data pointer (byte 2 onwards) ---------------------------------- */
+
+static inline uint8_t *s2108_get_frame_data(uint8_t *p_frame)
+{
+ return p_frame + S2108_FRAME_HEADER_SIZE;
+}
+
+static inline const uint8_t *s2108_get_frame_data_const(const uint8_t *p_frame)
+{
+ return p_frame + S2108_FRAME_HEADER_SIZE;
+}
+
+/* Total wire size of one frame (header + data bytes) */
+static inline uint16_t s2108_frame_size(const uint8_t *p_frame)
+{
+ return (uint16_t)S2108_FRAME_HEADER_SIZE + p_frame[1];
+}
+
+/*****************************************************************************
+ * Static Metadata Type 1 — Mastering Display Colour Volume (§5.3.2)
+ *
+ * Frame layout (26 data bytes, total frame = 28 bytes):
+ * data[0] = 0x89 (SEI payloadType = 137)
+ * data[1] = 0x18 (SEI payloadSize = 24)
+ * data[2..25] = mastering_display_colour_volume() from ITU-T H.265
+ *
+ * mastering_display_colour_volume() contains (all big-endian uint16):
+ * display_primaries_x[0..2] (chromaticity coords × 50000, CIE 1931)
+ * display_primaries_y[0..2]
+ * white_point_x
+ * white_point_y
+ * max_display_mastering_luminance (0.0001 cd/m² units)
+ * min_display_mastering_luminance
+ *****************************************************************************/
+
+/* Offset of the mastering_display_colour_volume() payload within frame data */
+#define S2108_STATIC1_MDC_OFFSET 2 /* skip payloadType + payloadSize */
+
+static inline void s2108_static1_init(uint8_t *p_frame)
+{
+ s2108_set_frame_type(p_frame, S2108_FRAME_TYPE_STATIC1);
+ s2108_set_frame_length(p_frame, S2108_STATIC1_FRAME_LENGTH);
+ p_frame[2] = S2108_STATIC1_SEI_TYPE;
+ p_frame[3] = S2108_STATIC1_SEI_SIZE;
+}
+
+/* Display primaries — index 0=green, 1=blue, 2=red per H.265 */
+static inline uint16_t s2108_static1_get_primary_x(const uint8_t *p_frame,
+ unsigned int i)
+{
+ const uint8_t *d = p_frame + 2 + S2108_STATIC1_MDC_OFFSET + i * 4;
+ return (d[0] << 8) | d[1];
+}
+
+static inline void s2108_static1_set_primary_x(uint8_t *p_frame,
+ unsigned int i, uint16_t val)
+{
+ uint8_t *d = p_frame + 2 + S2108_STATIC1_MDC_OFFSET + i * 4;
+ d[0] = val >> 8;
+ d[1] = val & 0xff;
+}
+
+static inline uint16_t s2108_static1_get_primary_y(const uint8_t *p_frame,
+ unsigned int i)
+{
+ const uint8_t *d = p_frame + 2 + S2108_STATIC1_MDC_OFFSET + i * 4 + 2;
+ return (d[0] << 8) | d[1];
+}
+
+static inline void s2108_static1_set_primary_y(uint8_t *p_frame,
+ unsigned int i, uint16_t val)
+{
+ uint8_t *d = p_frame + 2 + S2108_STATIC1_MDC_OFFSET + i * 4 + 2;
+ d[0] = val >> 8;
+ d[1] = val & 0xff;
+}
+
+static inline uint16_t s2108_static1_get_white_x(const uint8_t *p_frame)
+{
+ const uint8_t *d = p_frame + 2 + S2108_STATIC1_MDC_OFFSET + 12;
+ return (d[0] << 8) | d[1];
+}
+
+static inline void s2108_static1_set_white_x(uint8_t *p_frame, uint16_t val)
+{
+ uint8_t *d = p_frame + 2 + S2108_STATIC1_MDC_OFFSET + 12;
+ d[0] = val >> 8;
+ d[1] = val & 0xff;
+}
+
+static inline uint16_t s2108_static1_get_white_y(const uint8_t *p_frame)
+{
+ const uint8_t *d = p_frame + 2 + S2108_STATIC1_MDC_OFFSET + 14;
+ return (d[0] << 8) | d[1];
+}
+
+static inline void s2108_static1_set_white_y(uint8_t *p_frame, uint16_t val)
+{
+ uint8_t *d = p_frame + 2 + S2108_STATIC1_MDC_OFFSET + 14;
+ d[0] = val >> 8;
+ d[1] = val & 0xff;
+}
+
+/* max_display_mastering_luminance in units of 0.0001 cd/m² */
+static inline uint32_t s2108_static1_get_max_luma(const uint8_t *p_frame)
+{
+ const uint8_t *d = p_frame + 2 + S2108_STATIC1_MDC_OFFSET + 16;
+ return ((uint32_t)d[0] << 24) | ((uint32_t)d[1] << 16) |
+ ((uint32_t)d[2] << 8) | (uint32_t)d[3];
+}
+
+static inline void s2108_static1_set_max_luma(uint8_t *p_frame, uint32_t val)
+{
+ uint8_t *d = p_frame + 2 + S2108_STATIC1_MDC_OFFSET + 16;
+ d[0] = (val >> 24) & 0xff;
+ d[1] = (val >> 16) & 0xff;
+ d[2] = (val >> 8) & 0xff;
+ d[3] = val & 0xff;
+}
+
+/* min_display_mastering_luminance in units of 0.0001 cd/m² */
+static inline uint32_t s2108_static1_get_min_luma(const uint8_t *p_frame)
+{
+ const uint8_t *d = p_frame + 2 + S2108_STATIC1_MDC_OFFSET + 20;
+ return ((uint32_t)d[0] << 24) | ((uint32_t)d[1] << 16) |
+ ((uint32_t)d[2] << 8) | (uint32_t)d[3];
+}
+
+static inline void s2108_static1_set_min_luma(uint8_t *p_frame, uint32_t val)
+{
+ uint8_t *d = p_frame + 2 + S2108_STATIC1_MDC_OFFSET + 20;
+ d[0] = (val >> 24) & 0xff;
+ d[1] = (val >> 16) & 0xff;
+ d[2] = (val >> 8) & 0xff;
+ d[3] = val & 0xff;
+}
+
+/*****************************************************************************
+ * Static Metadata Type 2 — Content Light Level (§5.3.3)
+ *
+ * Frame layout (6 data bytes, total frame = 8 bytes):
+ * data[0] = 0x90 (SEI payloadType = 144)
+ * data[1] = 0x04 (SEI payloadSize = 4)
+ * data[2..3] = max_content_light_level (uint16 big-endian, cd/m²)
+ * data[4..5] = max_pic_average_light_level (uint16 big-endian, cd/m²)
+ *****************************************************************************/
+
+static inline void s2108_static2_init(uint8_t *p_frame)
+{
+ s2108_set_frame_type(p_frame, S2108_FRAME_TYPE_STATIC2);
+ s2108_set_frame_length(p_frame, S2108_STATIC2_FRAME_LENGTH);
+ p_frame[2] = S2108_STATIC2_SEI_TYPE;
+ p_frame[3] = S2108_STATIC2_SEI_SIZE;
+}
+
+/* MaxCLL — Maximum Content Light Level in cd/m² */
+static inline uint16_t s2108_static2_get_maxcll(const uint8_t *p_frame)
+{
+ return ((uint16_t)p_frame[4] << 8) | p_frame[5];
+}
+
+static inline void s2108_static2_set_maxcll(uint8_t *p_frame, uint16_t val)
+{
+ p_frame[4] = val >> 8;
+ p_frame[5] = val & 0xff;
+}
+
+/* MaxFALL — Maximum Frame Average Light Level in cd/m² */
+static inline uint16_t s2108_static2_get_maxfall(const uint8_t *p_frame)
+{
+ return ((uint16_t)p_frame[6] << 8) | p_frame[7];
+}
+
+static inline void s2108_static2_set_maxfall(uint8_t *p_frame, uint16_t val)
+{
+ p_frame[6] = val >> 8;
+ p_frame[7] = val & 0xff;
+}
+
+/*****************************************************************************
+ * Dynamic Metadata Type 1 — ATSC A/341 ST 2094-10 (§5.3.4)
+ *
+ * Frame layout (variable length):
+ * data[0] = 0x04 (SEI payloadType = 4, user_data_registered_itu_t_t35)
+ * data[1] = 9 + sizeof(ST2094-10_data) (SEI payloadSize)
+ * data[2] = 0xB5 (itu_t_t35_country_code, USA)
+ * data[3] = 0x00 (itu_t_t35_provider_code MSB, ATSC)
+ * data[4] = 0x31 (itu_t_t35_provider_code LSB, ATSC)
+ * data[5] = 0x47 (user_identifier MSB)
+ * data[6] = 0x41
+ * data[7] = 0x39
+ * data[8] = 0x34 (user_identifier LSB)
+ * data[9] = 0x09 (user_data_type_code for ST2094-10_data)
+ * data[10..] = ST2094-10_data()
+ * data[last] = 0xFF (marker_bits)
+ *
+ * Frame Length = S2108_DYNAMIC1_OVERHEAD + sizeof(ST2094-10_data).
+ *****************************************************************************/
+
+/* Initialise the fixed-format header bytes of a Dynamic Type 1 frame.
+ * st2094_10_size is the byte length of the raw ST2094-10_data() payload. */
+static inline void s2108_dynamic1_init(uint8_t *p_frame,
+ uint8_t st2094_10_size)
+{
+ s2108_set_frame_type(p_frame, S2108_FRAME_TYPE_DYNAMIC1);
+ s2108_set_frame_length(p_frame,
+ (uint8_t)(S2108_DYNAMIC1_OVERHEAD + st2094_10_size));
+ p_frame[2] = 0x04; /* SEI payloadType */
+ p_frame[3] = (uint8_t)(9 + st2094_10_size); /* SEI payloadSize */
+ p_frame[4] = S2108_DYNAMIC1_COUNTRY_CODE;
+ p_frame[5] = S2108_DYNAMIC1_PROVIDER_MSB;
+ p_frame[6] = S2108_DYNAMIC1_PROVIDER_LSB;
+ p_frame[7] = S2108_DYNAMIC1_USER_ID_0;
+ p_frame[8] = S2108_DYNAMIC1_USER_ID_1;
+ p_frame[9] = S2108_DYNAMIC1_USER_ID_2;
+ p_frame[10] = S2108_DYNAMIC1_USER_ID_3;
+ p_frame[11] = S2108_DYNAMIC1_TYPE_CODE;
+ /* marker byte lives at p_frame[12 + st2094_10_size] */
+ p_frame[12 + st2094_10_size] = S2108_DYNAMIC1_MARKER_BITS;
+}
+
+/* Pointer to the start of ST2094-10_data() within a Dynamic Type 1 frame */
+static inline uint8_t *s2108_dynamic1_get_payload(uint8_t *p_frame)
+{
+ return p_frame + 12; /* skip 2-byte frame header + 10 fixed bytes */
+}
+
+static inline const uint8_t *s2108_dynamic1_get_payload_const(
+ const uint8_t *p_frame)
+{
+ return p_frame + 12;
+}
+
+/*****************************************************************************
+ * Dynamic Metadata Type 5 — ETSI TS 103 433-1 SL-HDR1 (§5.3.5)
+ *
+ * Frame layout (variable length, sl_hdr_info size ≤ 251 bytes):
+ * data[0] = 0x04 (SEI payloadType = 4)
+ * data[1] = ceil(sl_hdr_info_bits / 8) (payload size in bytes)
+ * data[2..] = sl_hdr_info() from ETSI TS 103 433-1 Annex A.2,
+ * zero-padded to a byte boundary with a leading 1-bit
+ *
+ * Frame Length = 2 + ceil(sl_hdr_info_bits / 8).
+ *****************************************************************************/
+
+static inline void s2108_dynamic5_init(uint8_t *p_frame,
+ uint8_t sl_hdr_payload_bytes)
+{
+ s2108_set_frame_type(p_frame, S2108_FRAME_TYPE_DYNAMIC5);
+ s2108_set_frame_length(p_frame, (uint8_t)(2 + sl_hdr_payload_bytes));
+ p_frame[2] = 0x04; /* SEI payloadType */
+ p_frame[3] = sl_hdr_payload_bytes; /* SEI payloadSize */
+}
+
+/* Pointer to the start of sl_hdr_info() within a Dynamic Type 5 frame */
+static inline uint8_t *s2108_dynamic5_get_payload(uint8_t *p_frame)
+{
+ return p_frame + 4; /* skip 2-byte frame header + payloadType + payloadSize */
+}
+
+static inline const uint8_t *s2108_dynamic5_get_payload_const(
+ const uint8_t *p_frame)
+{
+ return p_frame + 4;
+}
+
+/*****************************************************************************
+ * Packet-level helpers
+ *****************************************************************************/
+
+/* Validate that p_frame points to a well-formed HDR/WCG Metadata Frame.
+ * Returns false if the frame type is reserved or the fixed-length types have
+ * an unexpected length. */
+static inline bool s2108_validate_frame(const uint8_t *p_frame)
+{
+ uint8_t type = s2108_get_frame_type(p_frame);
+ uint8_t length = s2108_get_frame_length(p_frame);
+
+ switch (type) {
+ case S2108_FRAME_TYPE_STATIC1:
+ return length == S2108_STATIC1_FRAME_LENGTH &&
+ p_frame[2] == S2108_STATIC1_SEI_TYPE &&
+ p_frame[3] == S2108_STATIC1_SEI_SIZE;
+ case S2108_FRAME_TYPE_STATIC2:
+ return length == S2108_STATIC2_FRAME_LENGTH &&
+ p_frame[2] == S2108_STATIC2_SEI_TYPE &&
+ p_frame[3] == S2108_STATIC2_SEI_SIZE;
+ case S2108_FRAME_TYPE_DYNAMIC1:
+ return length >= S2108_DYNAMIC1_OVERHEAD;
+ case S2108_FRAME_TYPE_DYNAMIC5:
+ return length >= 2 && length <= 253;
+ default:
+ return false; /* reserved */
+ }
+}
+
+/* Advance p_frame to the next HDR/WCG Metadata Frame within the same UDW
+ * payload. p_end must point one byte past the last UDW byte.
+ * Returns NULL when there are no more frames. */
+static inline const uint8_t *s2108_next_frame(const uint8_t *p_frame,
+ const uint8_t *p_end)
+{
+ const uint8_t *p_next = p_frame + s2108_frame_size(p_frame);
+ if (p_next + S2108_FRAME_HEADER_SIZE > p_end)
+ return NULL;
+ return p_next;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __BITSTREAM_SMPTE_2108_H__ */
View it on GitLab: https://code.videolan.org/videolan/bitstream/-/commit/cc05d361a27635d77ffabdc1045baf56a1bb4230
--
View it on GitLab: https://code.videolan.org/videolan/bitstream/-/commit/cc05d361a27635d77ffabdc1045baf56a1bb4230
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help
More information about the biTStream-devel
mailing list