aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJacob Lifshay <programmerjake@gmail.com>2025-07-22 23:07:14 -0700
committerJacob Lifshay <programmerjake@gmail.com>2025-08-08 03:04:42 -0700
commit1c85a3832afd39caac5ca76436162f790849e46b (patch)
treedd9fefd5a4c284546bb0b16205e9047409eaf94d
parenta566fcb9dc07262b0c6575466ce8cbc8b667e5b2 (diff)
downloadffmpeg-1c85a3832afd39caac5ca76436162f790849e46b.tar.gz
lavc: add API for manipulating SMPTE-436M VBI/ANC data
Signed-off-by: Jacob Lifshay <programmerjake@gmail.com>
-rw-r--r--.forgejo/pre-commit/ignored-words.txt1
-rwxr-xr-xconfigure1
-rw-r--r--doc/APIchanges4
-rw-r--r--libavcodec/Makefile2
-rw-r--r--libavcodec/smpte_436m.c475
-rw-r--r--libavcodec/smpte_436m.h254
-rw-r--r--libavcodec/smpte_436m_internal.h98
7 files changed, 835 insertions, 0 deletions
diff --git a/.forgejo/pre-commit/ignored-words.txt b/.forgejo/pre-commit/ignored-words.txt
index 9330ee43ef..870fd96be3 100644
--- a/.forgejo/pre-commit/ignored-words.txt
+++ b/.forgejo/pre-commit/ignored-words.txt
@@ -8,6 +8,7 @@ ALOG
ALS
als
ANC
+anc
ANS
ans
anull
diff --git a/configure b/configure
index 92a025fe98..01b8f596fd 100755
--- a/configure
+++ b/configure
@@ -2651,6 +2651,7 @@ CONFIG_EXTRA="
rv34dsp
scene_sad
sinewin
+ smpte_436m
snappy
srtp
startcode
diff --git a/doc/APIchanges b/doc/APIchanges
index 7443884885..0bdfb5cfbb 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,10 @@ The last version increases of all libraries were on 2025-03-28
API changes, most recent first:
+2025-07-29 - xxxxxxxxxx - lavc 62.10.100 - smpte_436m.h
+ Add a new public header smpte_436m.h with API for
+ manipulating AV_CODEC_ID_SMPTE_436M_ANC data.
+
2025-07-10 - xxxxxxxxxx - lavf 62.2.100
mxf [de]muxer now uses AV_CODEC_ID_SMPTE_436M_ANC for
the vbi_vanc_smpte_436M streams instead of AV_CODEC_ID_NONE.
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 33add16045..83ef92963a 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -19,6 +19,7 @@ HEADERS = ac3_parser.h \
mediacodec.h \
packet.h \
qsv.h \
+ smpte_436m.h \
vdpau.h \
version.h \
version_major.h \
@@ -167,6 +168,7 @@ OBJS-$(CONFIG_QSVENC) += qsvenc.o
OBJS-$(CONFIG_RANGECODER) += rangecoder.o
OBJS-$(CONFIG_RV34DSP) += rv34dsp.o
OBJS-$(CONFIG_SINEWIN) += sinewin.o
+OBJS-$(CONFIG_SMPTE_436M) += smpte_436m.o
OBJS-$(CONFIG_SNAPPY) += snappy.o
OBJS-$(CONFIG_STARTCODE) += startcode.o
OBJS-$(CONFIG_TEXTUREDSP) += texturedsp.o
diff --git a/libavcodec/smpte_436m.c b/libavcodec/smpte_436m.c
new file mode 100644
index 0000000000..b2324cac18
--- /dev/null
+++ b/libavcodec/smpte_436m.c
@@ -0,0 +1,475 @@
+/*
+ * MXF SMPTE-436M VBI/ANC parsing functions
+ * Copyright (c) 2025 Jacob Lifshay
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavcodec/smpte_436m.h"
+#include "bytestream.h"
+#include "libavcodec/packet.h"
+#include "libavutil/avassert.h"
+#include "libavutil/error.h"
+#include "libavutil/intreadwrite.h"
+
+static int validate_smpte_436m_anc_wrapping_type(AVSmpte436mWrappingType wrapping_type)
+{
+ switch (wrapping_type) {
+ case AV_SMPTE_436M_WRAPPING_TYPE_VANC_FRAME:
+ case AV_SMPTE_436M_WRAPPING_TYPE_VANC_FIELD_1:
+ case AV_SMPTE_436M_WRAPPING_TYPE_VANC_FIELD_2:
+ case AV_SMPTE_436M_WRAPPING_TYPE_VANC_PROGRESSIVE_FRAME:
+ case AV_SMPTE_436M_WRAPPING_TYPE_HANC_FRAME:
+ case AV_SMPTE_436M_WRAPPING_TYPE_HANC_FIELD_1:
+ case AV_SMPTE_436M_WRAPPING_TYPE_HANC_FIELD_2:
+ case AV_SMPTE_436M_WRAPPING_TYPE_HANC_PROGRESSIVE_FRAME:
+ return 0;
+ default:
+ return AVERROR_INVALIDDATA;
+ }
+}
+
+static int validate_smpte_436m_anc_payload_sample_coding(AVSmpte436mPayloadSampleCoding payload_sample_coding)
+{
+ switch (payload_sample_coding) {
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA_AND_COLOR_DIFF:
+ // not allowed for ANC packets
+ return AVERROR_INVALIDDATA;
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA_AND_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_WITH_PARITY_ERROR:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF_WITH_PARITY_ERROR:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF_WITH_PARITY_ERROR:
+ return 0;
+ default:
+ return AVERROR_INVALIDDATA;
+ }
+}
+
+int av_smpte_436m_coded_anc_validate(const AVSmpte436mCodedAnc *anc)
+{
+ int ret = validate_smpte_436m_anc_wrapping_type(anc->wrapping_type);
+ if (ret < 0)
+ return ret;
+ ret = validate_smpte_436m_anc_payload_sample_coding(anc->payload_sample_coding);
+ if (ret < 0)
+ return ret;
+ if (anc->payload_array_length > AV_SMPTE_436M_CODED_ANC_PAYLOAD_CAPACITY)
+ return AVERROR_INVALIDDATA;
+ ret = av_smpte_436m_coded_anc_payload_size(anc->payload_sample_coding, anc->payload_sample_count);
+ if (ret < 0)
+ return ret;
+ if (anc->payload_array_length < ret)
+ return AVERROR_INVALIDDATA;
+ return 0;
+}
+
+// Based off Table 7 (page 13) of:
+// https://pub.smpte.org/latest/st436/s436m-2006.pdf
+#define SMPTE_436M_ANC_ENTRY_HEADER_SIZE ( \
+ 2 /* line_number */ \
+ + 1 /* wrapping_type */ \
+ + 1 /* payload_sample_coding */ \
+ + 2 /* payload_sample_count */ \
+ + 4 /* payload_array_length */ \
+ + 4 /* payload_array_element_size */ \
+)
+
+/**
+ * Decode an ANC packet.
+ * @param[in] in Input bytes.
+ * @param[in] size the size of in.
+ * @param[out] anc the decoded ANC packet
+ * @return The number of read bytes on success, AVERROR_INVALIDDATA otherwise.
+ */
+static int smpte_436m_anc_decode_entry(const uint8_t *in, int size, AVSmpte436mCodedAnc *anc)
+{
+ // Based off Table 7 (page 13) of:
+ // https://pub.smpte.org/latest/st436/s436m-2006.pdf
+ if (SMPTE_436M_ANC_ENTRY_HEADER_SIZE > size)
+ return AVERROR_INVALIDDATA;
+ int needed_size = SMPTE_436M_ANC_ENTRY_HEADER_SIZE;
+ anc->line_number = AV_RB16(in);
+ in += 2;
+ anc->wrapping_type = AV_RB8(in);
+ in++;
+ anc->payload_sample_coding = AV_RB8(in);
+ in++;
+ anc->payload_sample_count = AV_RB16(in);
+ in += 2;
+ anc->payload_array_length = AV_RB32(in);
+ in += 4;
+ uint32_t payload_array_element_size = AV_RB32(in);
+ in += 4;
+ if (payload_array_element_size != 1)
+ return AVERROR_INVALIDDATA;
+ needed_size += anc->payload_array_length;
+ if (needed_size > size)
+ return AVERROR_INVALIDDATA;
+ if (anc->payload_array_length > AV_SMPTE_436M_CODED_ANC_PAYLOAD_CAPACITY)
+ return AVERROR_INVALIDDATA;
+ memcpy(anc->payload, in, anc->payload_array_length);
+ int ret = av_smpte_436m_coded_anc_validate(anc);
+ if (ret < 0)
+ return ret;
+ return needed_size;
+}
+
+/**
+ * Encode an ANC packet.
+ * @param[in] anc the ANC packet to encode
+ * @param[in] size the size of out. ignored if out is NULL.
+ * @param[out] out Output bytes. Doesn't write anything if out is NULL.
+ * @return the number of bytes written on success, AVERROR codes otherwise.
+ * If out is NULL, returns the number of bytes it would have written.
+ */
+static int smpte_436m_anc_encode_entry(uint8_t *out, int size, const AVSmpte436mCodedAnc *anc)
+{
+ // Based off Table 7 (page 13) of:
+ // https://pub.smpte.org/latest/st436/s436m-2006.pdf
+ if (anc->payload_array_length > AV_SMPTE_436M_CODED_ANC_PAYLOAD_CAPACITY)
+ return AVERROR_INVALIDDATA;
+ int needed_size = SMPTE_436M_ANC_ENTRY_HEADER_SIZE + (int)anc->payload_array_length;
+ if (!out)
+ return needed_size;
+ if (needed_size > size)
+ return AVERROR_BUFFER_TOO_SMALL;
+ AV_WB16(out, anc->line_number);
+ out += 2;
+ AV_WB8(out, anc->wrapping_type);
+ out++;
+ AV_WB8(out, anc->payload_sample_coding);
+ out++;
+ AV_WB16(out, anc->payload_sample_count);
+ out += 2;
+ AV_WB32(out, anc->payload_array_length);
+ out += 4;
+ AV_WB32(out, 1); // payload_array_element_size
+ out += 4;
+ memcpy(out, anc->payload, anc->payload_array_length);
+ return needed_size;
+}
+
+int av_smpte_436m_anc_encode(uint8_t *out, int size, int anc_packet_count, const AVSmpte436mCodedAnc *anc_packets)
+{
+ // Based off Table 7 (page 13) of:
+ // https://pub.smpte.org/latest/st436/s436m-2006.pdf
+ if (anc_packet_count < 0 || anc_packet_count >= (1L << 16) || size < 0)
+ return AVERROR_INVALIDDATA;
+
+ int needed_size = 2;
+ if (out) {
+ if (size < needed_size)
+ return AVERROR_BUFFER_TOO_SMALL;
+ AV_WB16(out, anc_packet_count);
+ out += 2;
+ size -= 2;
+ }
+ for (int i = 0; i < anc_packet_count; i++) {
+ int ret = smpte_436m_anc_encode_entry(out, size, &anc_packets[i]);
+ if (ret < 0)
+ return ret;
+ needed_size += ret;
+ if (out) {
+ size -= ret;
+ out += ret;
+ }
+ }
+ return needed_size;
+}
+
+int av_smpte_436m_anc_append(AVPacket *pkt, int anc_packet_count, const AVSmpte436mCodedAnc *anc_packets)
+{
+ int final_packet_count = 0;
+ int write_start = 2;
+ if (pkt->size >= 2) {
+ final_packet_count = AV_RB16(pkt->data);
+ write_start = pkt->size;
+ } else if (pkt->size != 0) // if packet isn't empty
+ return AVERROR_INVALIDDATA;
+ if (anc_packet_count < 0 || anc_packet_count >= (1L << 16))
+ return AVERROR_INVALIDDATA;
+ final_packet_count += anc_packet_count;
+ if (final_packet_count >= (1L << 16))
+ return AVERROR_INVALIDDATA;
+ int ret, additional_size = write_start - pkt->size;
+ for (int i = 0; i < anc_packet_count; i++) {
+ ret = smpte_436m_anc_encode_entry(NULL, 0, &anc_packets[i]);
+ if (ret < 0)
+ return ret;
+ additional_size += ret;
+ }
+ ret = av_grow_packet(pkt, additional_size);
+ if (ret < 0)
+ return ret;
+ for (int i = 0; i < anc_packet_count; i++) {
+ ret = smpte_436m_anc_encode_entry(pkt->data + write_start, pkt->size - write_start, &anc_packets[i]);
+ av_assert0(ret >= 0);
+ write_start += ret;
+ }
+ AV_WB16(pkt->data, final_packet_count);
+ return 0;
+}
+
+int av_smpte_436m_anc_iter_init(AVSmpte436mAncIterator *iter, const uint8_t *buf, int buf_size)
+{
+ // Based off Table 7 (page 13) of:
+ // https://pub.smpte.org/latest/st436/s436m-2006.pdf
+ if (buf_size < 2)
+ return AVERROR_INVALIDDATA;
+ *iter = (AVSmpte436mAncIterator){
+ .anc_packets_left = AV_RB16(buf),
+ .size_left = buf_size - 2,
+ .data_left = buf + 2,
+ };
+ if (iter->anc_packets_left > iter->size_left)
+ return AVERROR_INVALIDDATA;
+ return 0;
+}
+
+int av_smpte_436m_anc_iter_next(AVSmpte436mAncIterator *iter, AVSmpte436mCodedAnc *anc)
+{
+ if (iter->anc_packets_left <= 0)
+ return AVERROR_EOF;
+ iter->anc_packets_left--;
+ int ret = smpte_436m_anc_decode_entry(iter->data_left, iter->size_left, anc);
+ if (ret < 0) {
+ iter->anc_packets_left = 0;
+ return ret;
+ }
+ iter->data_left += ret;
+ iter->size_left -= ret;
+ return 0;
+}
+
+int av_smpte_436m_coded_anc_payload_size(AVSmpte436mPayloadSampleCoding sample_coding, uint16_t sample_count)
+{
+ if (sample_count > AV_SMPTE_436M_CODED_ANC_SAMPLE_CAPACITY)
+ return AVERROR_INVALIDDATA;
+ switch (sample_coding) {
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA_AND_COLOR_DIFF:
+ return AVERROR_INVALIDDATA;
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_WITH_PARITY_ERROR:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF_WITH_PARITY_ERROR:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF_WITH_PARITY_ERROR:
+ // "The Payload Byte Array shall be padded to achieve UInt32 alignment."
+ // section 4.4 of https://pub.smpte.org/latest/st436/s436m-2006.pdf
+ return (sample_count + 3) & -4;
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA_AND_COLOR_DIFF:
+ // encoded with 3 10-bit samples in a UInt32.
+ // "The Payload Byte Array shall be padded to achieve UInt32 alignment."
+ // section 4.4 of https://pub.smpte.org/latest/st436/s436m-2006.pdf
+ return 4 * ((sample_count + 2) / 3);
+ default:
+ return AVERROR_INVALIDDATA;
+ }
+}
+
+int av_smpte_291m_anc_8bit_decode(AVSmpte291mAnc8bit *out,
+ AVSmpte436mPayloadSampleCoding sample_coding,
+ uint16_t sample_count,
+ const uint8_t *payload,
+ void *log_ctx)
+{
+ switch (sample_coding) {
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA_AND_COLOR_DIFF:
+ return AVERROR_INVALIDDATA;
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_WITH_PARITY_ERROR:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF_WITH_PARITY_ERROR:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF_WITH_PARITY_ERROR:
+ {
+ if (sample_count < 3)
+ return AVERROR_INVALIDDATA;
+ out->did = *payload++;
+ out->sdid_or_dbn = *payload++;
+ out->data_count = *payload++;
+ if (sample_count < out->data_count + 3)
+ return AVERROR_INVALIDDATA;
+ memcpy(out->payload, payload, out->data_count);
+ // the checksum isn't stored in 8-bit mode, so calculate it.
+ av_smpte_291m_anc_8bit_fill_checksum(out);
+ return 0;
+ }
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA_AND_COLOR_DIFF:
+ av_log(log_ctx,
+ AV_LOG_ERROR,
+ "decoding an ANC packet using the 10-bit SMPTE 436M sample coding isn't implemented.\n");
+ return AVERROR_PATCHWELCOME;
+ default:
+ return AVERROR_INVALIDDATA;
+ }
+}
+
+void av_smpte_291m_anc_8bit_fill_checksum(AVSmpte291mAnc8bit *anc)
+{
+ uint8_t checksum = anc->did + anc->sdid_or_dbn + anc->data_count;
+ for (unsigned i = 0; i < anc->data_count; i++) {
+ checksum += anc->payload[i];
+ }
+ anc->checksum = checksum;
+}
+
+int av_smpte_291m_anc_8bit_get_sample_count(const AVSmpte291mAnc8bit *anc,
+ AVSmpte436mPayloadSampleCoding sample_coding,
+ void *log_ctx)
+{
+ switch (sample_coding) {
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA_AND_COLOR_DIFF:
+ return AVERROR_INVALIDDATA;
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_WITH_PARITY_ERROR:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF_WITH_PARITY_ERROR:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF_WITH_PARITY_ERROR:
+ // 3 for did, sdid_or_dbn, and data_count; checksum isn't stored in 8-bit modes
+ return 3 + anc->data_count;
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA_AND_COLOR_DIFF:
+ av_log(log_ctx,
+ AV_LOG_ERROR,
+ "encoding an ANC packet using the 10-bit SMPTE 436M sample coding isn't implemented.\n");
+ return AVERROR_PATCHWELCOME;
+ default:
+ return AVERROR_INVALIDDATA;
+ }
+}
+
+int av_smpte_291m_anc_8bit_encode(AVSmpte436mCodedAnc *out,
+ uint16_t line_number,
+ AVSmpte436mWrappingType wrapping_type,
+ AVSmpte436mPayloadSampleCoding sample_coding,
+ const AVSmpte291mAnc8bit *payload,
+ void *log_ctx)
+{
+ out->line_number = line_number;
+ out->wrapping_type = wrapping_type;
+ out->payload_sample_coding = sample_coding;
+
+ int ret = av_smpte_291m_anc_8bit_get_sample_count(payload, sample_coding, log_ctx);
+ if (ret < 0)
+ return ret;
+
+ out->payload_sample_count = ret;
+
+ ret = av_smpte_436m_coded_anc_payload_size(sample_coding, out->payload_sample_count);
+ if (ret < 0)
+ return ret;
+
+ out->payload_array_length = ret;
+
+ switch (sample_coding) {
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA_AND_COLOR_DIFF:
+ return AVERROR_INVALIDDATA;
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_WITH_PARITY_ERROR:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF_WITH_PARITY_ERROR:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF_WITH_PARITY_ERROR:
+ {
+ // fill trailing padding with zeros
+ av_assert0(out->payload_array_length >= 4);
+ memset(out->payload + out->payload_array_length - 4, 0, 4);
+
+ out->payload[0] = payload->did;
+ out->payload[1] = payload->sdid_or_dbn;
+ out->payload[2] = payload->data_count;
+
+ memcpy(out->payload + 3, payload->payload, payload->data_count);
+ return 0;
+ }
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_COLOR_DIFF:
+ case AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA_AND_COLOR_DIFF:
+ av_log(log_ctx,
+ AV_LOG_ERROR,
+ "encoding an ANC packet using the 10-bit SMPTE 436M sample coding isn't implemented.\n");
+ return AVERROR_PATCHWELCOME;
+ default:
+ return AVERROR_INVALIDDATA;
+ }
+}
+
+int av_smpte_291m_anc_8bit_extract_cta_708(const AVSmpte291mAnc8bit *anc, uint8_t *cc_data, void *log_ctx)
+{
+ if (anc->did != AV_SMPTE_291M_ANC_DID_CTA_708 || anc->sdid_or_dbn != AV_SMPTE_291M_ANC_SDID_CTA_708)
+ return AVERROR(EAGAIN);
+ GetByteContext gb;
+ bytestream2_init(&gb, anc->payload, anc->data_count);
+ // based on Caption Distribution Packet (CDP) Definition:
+ // https://pub.smpte.org/latest/st334-2/st0334-2-2015.pdf
+ uint16_t cdp_identifier = bytestream2_get_be16(&gb);
+ if (cdp_identifier != 0x9669) { // CDPs always have this value
+ av_log(log_ctx, AV_LOG_ERROR, "wrong cdp identifier %x\n", cdp_identifier);
+ return AVERROR_INVALIDDATA;
+ }
+ bytestream2_get_byte(&gb); // cdp_length
+ bytestream2_get_byte(&gb); // cdp_frame_rate and reserved
+ bytestream2_get_byte(&gb); // flags
+ bytestream2_get_be16(&gb); // cdp_hdr_sequence_cntr
+ unsigned section_id = bytestream2_get_byte(&gb);
+
+ const unsigned TIME_CODE_SECTION_ID = 0x71;
+ if (section_id == TIME_CODE_SECTION_ID) {
+ bytestream2_skip(&gb, 4); // skip time code section
+ section_id = bytestream2_get_byte(&gb);
+ }
+ const unsigned CC_DATA_SECTION_ID = 0x72;
+ if (section_id == CC_DATA_SECTION_ID) {
+ if (bytestream2_get_bytes_left(&gb) < 1)
+ goto too_short;
+ // 0x1F for lower 5 bits, upper 3 bits are marker bits
+ unsigned cc_count = bytestream2_get_byte(&gb) & 0x1F;
+ unsigned data_length = cc_count * 3; // EIA-608/CTA-708 triples are 3 bytes long
+ if (bytestream2_get_bytes_left(&gb) < data_length)
+ goto too_short;
+ if (cc_data)
+ bytestream2_get_bufferu(&gb, cc_data, data_length);
+ return cc_count;
+ }
+ return AVERROR(EAGAIN);
+
+too_short:
+ av_log(log_ctx, AV_LOG_ERROR, "not enough bytes in cdp\n");
+ return AVERROR_INVALIDDATA;
+}
diff --git a/libavcodec/smpte_436m.h b/libavcodec/smpte_436m.h
new file mode 100644
index 0000000000..9c0e2a5a05
--- /dev/null
+++ b/libavcodec/smpte_436m.h
@@ -0,0 +1,254 @@
+/*
+ * MXF SMPTE-436M VBI/ANC parsing functions
+ * Copyright (c) 2025 Jacob Lifshay
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_SMPTE_436M_H
+#define AVCODEC_SMPTE_436M_H
+
+#include <stdint.h>
+
+/**
+ * Iterator over the ANC packets in a single AV_CODEC_ID_SMPTE_436M_ANC AVPacket's data
+ */
+typedef struct AVSmpte436mAncIterator {
+ uint16_t anc_packets_left;
+ int size_left;
+ const uint8_t *data_left;
+} AVSmpte436mAncIterator;
+
+/**
+ * Wrapping Type from Table 7 (page 13) of:
+ * https://pub.smpte.org/latest/st436/s436m-2006.pdf
+ */
+typedef enum AVSmpte436mWrappingType
+{
+ AV_SMPTE_436M_WRAPPING_TYPE_VANC_FRAME = 1,
+ AV_SMPTE_436M_WRAPPING_TYPE_VANC_FIELD_1 = 2,
+ AV_SMPTE_436M_WRAPPING_TYPE_VANC_FIELD_2 = 3,
+ AV_SMPTE_436M_WRAPPING_TYPE_VANC_PROGRESSIVE_FRAME = 4,
+ AV_SMPTE_436M_WRAPPING_TYPE_HANC_FRAME = 0x11,
+ AV_SMPTE_436M_WRAPPING_TYPE_HANC_FIELD_1 = 0x12,
+ AV_SMPTE_436M_WRAPPING_TYPE_HANC_FIELD_2 = 0x13,
+ AV_SMPTE_436M_WRAPPING_TYPE_HANC_PROGRESSIVE_FRAME = 0x14,
+ /** not a real wrapping type, just here to guarantee the enum is big enough */
+ AV_SMPTE_436M_WRAPPING_TYPE_MAX = 0xFF,
+} AVSmpte436mWrappingType;
+
+/**
+ * Payload Sample Coding from Table 4 (page 10) and Table 7 (page 13) of:
+ * https://pub.smpte.org/latest/st436/s436m-2006.pdf
+ */
+typedef enum AVSmpte436mPayloadSampleCoding
+{
+ /** only used for VBI */
+ AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA = 1,
+ /** only used for VBI */
+ AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_COLOR_DIFF = 2,
+ /** only used for VBI */
+ AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA_AND_COLOR_DIFF = 3,
+ /** used for VBI and ANC */
+ AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA = 4,
+ /** used for VBI and ANC */
+ AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF = 5,
+ /** used for VBI and ANC */
+ AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF = 6,
+ /** used for VBI and ANC */
+ AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA = 7,
+ /** used for VBI and ANC */
+ AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_COLOR_DIFF = 8,
+ /** used for VBI and ANC */
+ AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA_AND_COLOR_DIFF = 9,
+ /** only used for ANC */
+ AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_WITH_PARITY_ERROR = 10,
+ /** only used for ANC */
+ AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF_WITH_PARITY_ERROR = 11,
+ /** only used for ANC */
+ AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF_WITH_PARITY_ERROR = 12,
+ /** not a real sample coding, just here to guarantee the enum is big enough */
+ AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_MAX = 0xFF,
+} AVSmpte436mPayloadSampleCoding;
+
+/** the payload capacity of AVSmpte291mAnc8bit (and of AVSmpte291mAnc10bit when that gets added) */
+#define AV_SMPTE_291M_ANC_PAYLOAD_CAPACITY 0xFF
+
+/**
+ * An ANC packet with an 8-bit payload.
+ * This can be decoded from AVSmpte436mCodedAnc::payload.
+ *
+ * Note: Some ANC packets need a 10-bit payload, if stored in this struct,
+ * the most-significant 2 bits of each sample are discarded.
+ */
+typedef struct AVSmpte291mAnc8bit {
+ uint8_t did;
+ uint8_t sdid_or_dbn;
+ uint8_t data_count;
+ uint8_t payload[AV_SMPTE_291M_ANC_PAYLOAD_CAPACITY];
+ uint8_t checksum;
+} AVSmpte291mAnc8bit;
+
+/** max number of samples that can be stored in the payload of AVSmpte436mCodedAnc */
+#define AV_SMPTE_436M_CODED_ANC_SAMPLE_CAPACITY \
+ (AV_SMPTE_291M_ANC_PAYLOAD_CAPACITY + 4) /* 4 for did, sdid_or_dbn, data_count, and checksum */
+/** max number of bytes that can be stored in the payload of AVSmpte436mCodedAnc */
+#define AV_SMPTE_436M_CODED_ANC_PAYLOAD_CAPACITY (((AV_SMPTE_436M_CODED_ANC_SAMPLE_CAPACITY + 2) / 3) * 4)
+
+/**
+ * An encoded ANC packet within a single AV_CODEC_ID_SMPTE_436M_ANC AVPacket's data.
+ * The repeated section of Table 7 (page 13) of:
+ * https://pub.smpte.org/latest/st436/s436m-2006.pdf
+ */
+typedef struct AVSmpte436mCodedAnc {
+ uint16_t line_number;
+ AVSmpte436mWrappingType wrapping_type;
+ AVSmpte436mPayloadSampleCoding payload_sample_coding;
+ uint16_t payload_sample_count;
+ uint32_t payload_array_length;
+ /** the payload, has size payload_array_length.
+ * can be decoded into AVSmpte291mAnc8bit
+ */
+ uint8_t payload[AV_SMPTE_436M_CODED_ANC_PAYLOAD_CAPACITY];
+} AVSmpte436mCodedAnc;
+
+/**
+ * Validate a AVSmpte436mCodedAnc structure. Doesn't check if the payload is valid.
+ * @param[in] anc ANC packet to validate
+ * @return 0 on success, AVERROR codes otherwise.
+ */
+int av_smpte_436m_coded_anc_validate(const AVSmpte436mCodedAnc *anc);
+
+/**
+ * Encode ANC packets into a single AV_CODEC_ID_SMPTE_436M_ANC AVPacket's data.
+ * @param[in] anc_packet_count number of ANC packets to encode
+ * @param[in] anc_packets the ANC packets to encode
+ * @param[in] size the size of out. ignored if out is NULL.
+ * @param[out] out Output bytes. Doesn't write anything if out is NULL.
+ * @return the number of bytes written on success, AVERROR codes otherwise.
+ * If out is NULL, returns the number of bytes it would have written.
+ */
+int av_smpte_436m_anc_encode(uint8_t *out, int size, int anc_packet_count, const AVSmpte436mCodedAnc *anc_packets);
+
+struct AVPacket;
+
+/**
+ * Append more ANC packets to a single AV_CODEC_ID_SMPTE_436M_ANC AVPacket's data.
+ * @param[in] anc_packet_count number of ANC packets to encode
+ * @param[in] anc_packets the ANC packets to encode
+ * @param pkt the AVPacket to append to.
+ * it must either be size 0 or contain valid SMPTE_436M_ANC data.
+ * @return 0 on success, AVERROR codes otherwise.
+ */
+int av_smpte_436m_anc_append(struct AVPacket *pkt, int anc_packet_count, const AVSmpte436mCodedAnc *anc_packets);
+
+/**
+ * Set up iteration over the ANC packets in a single AV_CODEC_ID_SMPTE_436M_ANC AVPacket's data.
+ * @param[in] buf Pointer to the data from a AV_CODEC_ID_SMPTE_436M_ANC AVPacket.
+ * @param[in] buf_size Size of the data from a AV_CODEC_ID_SMPTE_436M_ANC AVPacket.
+ * @param[out] iter Pointer to the iterator.
+ * @return 0 on success, AVERROR codes otherwise.
+ */
+int av_smpte_436m_anc_iter_init(AVSmpte436mAncIterator *iter, const uint8_t *buf, int buf_size);
+
+/**
+ * Get the next ANC packet from the iterator, advancing the iterator.
+ * @param[in,out] iter Pointer to the iterator.
+ * @param[out] anc The returned ANC packet.
+ * @return 0 on success, AVERROR_EOF when the iterator has reached the end, AVERROR codes otherwise.
+ */
+int av_smpte_436m_anc_iter_next(AVSmpte436mAncIterator *iter, AVSmpte436mCodedAnc *anc);
+
+/**
+ * Get the minimum number of bytes needed to store a AVSmpte436mCodedAnc payload.
+ * @param sample_coding the payload sample coding
+ * @param sample_count the number of samples stored in the payload
+ * @return returns the minimum number of bytes needed, on error returns < 0.
+ * always <= SMPTE_436M_CODED_ANC_PAYLOAD_CAPACITY
+ */
+int av_smpte_436m_coded_anc_payload_size(AVSmpte436mPayloadSampleCoding sample_coding, uint16_t sample_count);
+
+/**
+ * Decode a AVSmpte436mCodedAnc payload into AVSmpte291mAnc8bit
+ * @param[in] sample_coding the payload sample coding
+ * @param[in] sample_count the number of samples stored in the payload
+ * @param[in] payload the bytes storing the payload,
+ * the needed size can be obtained from
+ avpriv_smpte_436m_coded_anc_payload_size
+ * @param[in] log_ctx context pointer for av_log
+ * @param[out] out The decoded ANC packet.
+ * @return returns 0 on success, otherwise < 0.
+ */
+int av_smpte_291m_anc_8bit_decode(AVSmpte291mAnc8bit *out,
+ AVSmpte436mPayloadSampleCoding sample_coding,
+ uint16_t sample_count,
+ const uint8_t *payload,
+ void *log_ctx);
+
+/**
+ * Fill in the correct checksum for a AVSmpte291mAnc8bit
+ * @param[in,out] anc The ANC packet.
+ */
+void av_smpte_291m_anc_8bit_fill_checksum(AVSmpte291mAnc8bit *anc);
+
+/**
+ * Compute the sample count needed to encode a AVSmpte291mAnc8bit into a AVSmpte436mCodedAnc payload
+ * @param[in] anc The ANC packet.
+ * @param[in] sample_coding The sample coding.
+ * @param[in] log_ctx context pointer for av_log
+ * @return returns the sample count on success, otherwise < 0.
+ */
+int av_smpte_291m_anc_8bit_get_sample_count(const AVSmpte291mAnc8bit *anc,
+ AVSmpte436mPayloadSampleCoding sample_coding,
+ void *log_ctx);
+
+/**
+ * Encode a AVSmpte291mAnc8bit into a AVSmpte436mCodedAnc
+ * @param[in] line_number the line number the ANC packet is on
+ * @param[in] wrapping_type the wrapping type
+ * @param[in] sample_coding the payload sample coding
+ * @param[in] payload the ANC packet to encode.
+ * @param[in] log_ctx context pointer for av_log
+ * @param[out] out The encoded ANC packet.
+ * @return returns 0 on success, otherwise < 0.
+ */
+int av_smpte_291m_anc_8bit_encode(AVSmpte436mCodedAnc *out,
+ uint16_t line_number,
+ AVSmpte436mWrappingType wrapping_type,
+ AVSmpte436mPayloadSampleCoding sample_coding,
+ const AVSmpte291mAnc8bit *payload,
+ void *log_ctx);
+
+/** AVSmpte291mAnc8bit::did when carrying CTA-708 data (for AV_CODEC_ID_EIA_608) */
+#define AV_SMPTE_291M_ANC_DID_CTA_708 0x61
+
+/** AVSmpte291mAnc8bit::sdid_or_dbn when carrying CTA-708 data (for AV_CODEC_ID_EIA_608) */
+#define AV_SMPTE_291M_ANC_SDID_CTA_708 0x1
+
+/**
+ * Try to decode an ANC packet into EIA-608/CTA-708 data (AV_CODEC_ID_EIA_608). This
+ * @param[in] anc The ANC packet.
+ * @param[in] log_ctx Context pointer for av_log
+ * @param[out] cc_data the buffer to store the extracted EIA-608/CTA-708 data,
+ * you can pass NULL to not store the data.
+ * the required size is 3 * cc_count bytes.
+ * SMPTE_291M_ANC_PAYLOAD_CAPACITY is always enough size.
+ * @return returns cc_count (>= 0) on success, AVERROR(EAGAIN) if it wasn't a CTA-708 ANC packet, < 0 on error.
+ */
+int av_smpte_291m_anc_8bit_extract_cta_708(const AVSmpte291mAnc8bit *anc, uint8_t *cc_data, void *log_ctx);
+
+#endif /* AVCODEC_SMPTE_436M_H */
diff --git a/libavcodec/smpte_436m_internal.h b/libavcodec/smpte_436m_internal.h
new file mode 100644
index 0000000000..7481949fb6
--- /dev/null
+++ b/libavcodec/smpte_436m_internal.h
@@ -0,0 +1,98 @@
+/*
+ * MXF SMPTE-436M VBI/ANC internals
+ * Copyright (c) 2025 Jacob Lifshay
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_SMPTE_436M_INTERNAL_H
+#define AVCODEC_SMPTE_436M_INTERNAL_H
+
+#include "smpte_436m.h"
+
+// clang-format off
+#define FF_SMPTE_436M_WRAPPING_TYPE_VANC_AVOPTIONS(flags, unit_name) \
+ { "vanc_frame", "VANC frame (interlaced or segmented progressive frame)", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_WRAPPING_TYPE_VANC_FRAME}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "vanc_field_1", "VANC field 1", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_WRAPPING_TYPE_VANC_FIELD_1}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "vanc_field_2", "VANC field 2", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_WRAPPING_TYPE_VANC_FIELD_2}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "vanc_progressive_frame", "VANC progressive frame", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_WRAPPING_TYPE_VANC_PROGRESSIVE_FRAME}, 0, 0xFF, flags, .unit = unit_name }
+
+#define FF_SMPTE_436M_WRAPPING_TYPE_HANC_AVOPTIONS(flags, unit_name) \
+ { "hanc_frame", "HANC frame (interlaced or segmented progressive frame)", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_WRAPPING_TYPE_HANC_FRAME}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "hanc_field_1", "HANC field 1", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_WRAPPING_TYPE_HANC_FIELD_1}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "hanc_field_2", "HANC field 2", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_WRAPPING_TYPE_HANC_FIELD_2}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "hanc_progressive_frame", "HANC progressive frame", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_WRAPPING_TYPE_HANC_PROGRESSIVE_FRAME}, 0, 0xFF, flags, .unit = unit_name }
+
+#define FF_SMPTE_436M_WRAPPING_TYPE_AVOPTIONS(flags, unit_name) \
+ FF_SMPTE_436M_WRAPPING_TYPE_VANC_AVOPTIONS(flags, unit_name), \
+ FF_SMPTE_436M_WRAPPING_TYPE_HANC_AVOPTIONS(flags, unit_name)
+// clang-format on
+
+// clang-format off
+#define FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_ONLY_IN_VBI_AVOPTIONS(flags, unit_name) \
+ { "1bit_luma", "1-bit component luma samples", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "1bit_color_diff", "1-bit component color difference samples", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_COLOR_DIFF}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "1bit_luma_and_color_diff", "1-bit component luma and color difference samples", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_1BIT_LUMA_AND_COLOR_DIFF}, 0, 0xFF, flags, .unit = unit_name }
+
+#define FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_SHARED_AVOPTIONS(flags, unit_name) \
+ { "8bit_luma", "8-bit component luma samples", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "8bit_color_diff", "8-bit component color difference samples", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "8bit_luma_and_color_diff", "8-bit component luma and color difference samples", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "10bit_luma", "10-bit component luma samples", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "10bit_color_diff", "10-bit component color difference samples", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_COLOR_DIFF}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "10bit_luma_and_color_diff", "10-bit component luma and color difference samples", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_10BIT_LUMA_AND_COLOR_DIFF}, 0, 0xFF, flags, .unit = unit_name }
+
+#define FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_ONLY_IN_ANC_AVOPTIONS(flags, unit_name) \
+ { "8bit_luma_parity_error", "8-bit component luma samples with parity error", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_WITH_PARITY_ERROR}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "8bit_color_diff_parity_error", "8-bit component color difference samples with parity error", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_COLOR_DIFF_WITH_PARITY_ERROR}, 0, 0xFF, flags, .unit = unit_name }, \
+ { "8bit_luma_and_color_diff_parity_error", "8-bit component luma and color difference samples with parity error", 0, AV_OPT_TYPE_CONST, \
+ {.i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA_AND_COLOR_DIFF_WITH_PARITY_ERROR}, 0, 0xFF, flags, .unit = unit_name }
+
+#define FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_VBI_AVOPTIONS(flags, unit_name) \
+ FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_ONLY_IN_VBI_AVOPTIONS(flags, unit_name), \
+ FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_SHARED_AVOPTIONS(flags, unit_name)
+
+#define FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_ANC_AVOPTIONS(flags, unit_name) \
+ FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_SHARED_AVOPTIONS(flags, unit_name), \
+ FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_ONLY_IN_ANC_AVOPTIONS(flags, unit_name)
+
+#define FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_AVOPTIONS(flags, unit_name) \
+ FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_ONLY_IN_VBI_AVOPTIONS(flags, unit_name), \
+ FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_SHARED_AVOPTIONS(flags, unit_name), \
+ FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_ONLY_IN_ANC_AVOPTIONS(flags, unit_name)
+// clang-format on
+
+#endif /* AVCODEC_SMPTE_436M_INTERNAL_H */