| @@ -0,0 +1,277 @@ |
| | 1 | /* |
| | 2 | * EIA-608 to MXF SMPTE-436M ANC bitstream filter |
| | 3 | * Copyright (c) 2025 Jacob Lifshay |
| | 4 | * |
| | 5 | * This file is part of FFmpeg. |
| | 6 | * |
| | 7 | * FFmpeg is free software; you can redistribute it and/or |
| | 8 | * modify it under the terms of the GNU Lesser General Public |
| | 9 | * License as published by the Free Software Foundation; either |
| | 10 | * version 2.1 of the License, or (at your option) any later version. |
| | 11 | * |
| | 12 | * FFmpeg is distributed in the hope that it will be useful, |
| | 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| | 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| | 15 | * Lesser General Public License for more details. |
| | 16 | * |
| | 17 | * You should have received a copy of the GNU Lesser General Public |
| | 18 | * License along with FFmpeg; if not, write to the Free Software |
| | 19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| | 20 | */ |
| | 21 | |
| | 22 | #include "bsf.h" |
| | 23 | #include "bsf_internal.h" |
| | 24 | #include "codec_id.h" |
| | 25 | #include "libavcodec/smpte_436m.h" |
| | 26 | #include "libavcodec/smpte_436m_internal.h" |
| | 27 | #include "libavutil/avassert.h" |
| | 28 | #include "libavutil/avutil.h" |
| | 29 | #include "libavutil/error.h" |
| | 30 | #include "libavutil/intreadwrite.h" |
| | 31 | #include "libavutil/macros.h" |
| | 32 | #include "libavutil/opt.h" |
| | 33 | #include "libavutil/rational.h" |
| | 34 | |
| | 35 | typedef struct EIA608ToSMPTE436MContext { |
| | 36 | const AVClass *class; |
| | 37 | unsigned line_number; |
| | 38 | unsigned cdp_sequence_cntr; |
| | 39 | unsigned wrapping_type_opt; |
| | 40 | unsigned sample_coding_opt; |
| | 41 | AVSmpte436mWrappingType wrapping_type; |
| | 42 | AVSmpte436mPayloadSampleCoding sample_coding; |
| | 43 | AVRational cdp_frame_rate; |
| | 44 | uint8_t cdp_frame_rate_byte; |
| | 45 | } EIA608ToSMPTE436MContext; |
| | 46 | |
| | 47 | // clang-format off |
| | 48 | static const AVSmpte291mAnc8bit test_anc = { |
| | 49 | .did = 0x61, |
| | 50 | .sdid_or_dbn = 0x01, |
| | 51 | .data_count = 0x49, |
| | 52 | .payload = { |
| | 53 | // header |
| | 54 | 0x96, 0x69, 0x49, 0x7F, 0x43, 0xFA, 0x8D, 0x72, 0xF4, |
| | 55 | |
| | 56 | // 608 triples |
| | 57 | 0xFC, 0x80, 0x80, 0xFD, 0x80, 0x80, |
| | 58 | |
| | 59 | // 708 padding |
| | 60 | 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, |
| | 61 | 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, |
| | 62 | 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, |
| | 63 | 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, |
| | 64 | 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, |
| | 65 | 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, |
| | 66 | |
| | 67 | // footer |
| | 68 | 0x74, 0xFA, 0x8D, 0x81, |
| | 69 | }, |
| | 70 | .checksum = 0xAB, |
| | 71 | }; |
| | 72 | // clang-format on |
| | 73 | |
| | 74 | static av_cold int ff_eia608_to_smpte436m_init(AVBSFContext *ctx) |
| | 75 | { |
| | 76 | EIA608ToSMPTE436MContext *priv = ctx->priv_data; |
| | 77 | |
| | 78 | priv->wrapping_type = priv->wrapping_type_opt; |
| | 79 | priv->sample_coding = priv->sample_coding_opt; |
| | 80 | |
| | 81 | // validate we can handle the selected wrapping type and sample coding |
| | 82 | |
| | 83 | AVSmpte436mCodedAnc coded_anc; |
| | 84 | |
| | 85 | int ret = av_smpte_291m_anc_8bit_encode( |
| | 86 | &coded_anc, priv->line_number, priv->wrapping_type, priv->sample_coding, &test_anc, ctx); |
| | 87 | if (ret < 0) |
| | 88 | return ret; |
| | 89 | |
| | 90 | ctx->par_out->codec_type = AVMEDIA_TYPE_DATA; |
| | 91 | ctx->par_out->codec_id = AV_CODEC_ID_SMPTE_436M_ANC; |
| | 92 | |
| | 93 | static const struct { |
| | 94 | AVRational frame_rate; |
| | 95 | uint8_t cdp_frame_rate; |
| | 96 | } known_frame_rates[] = { |
| | 97 | { .frame_rate = { .num = 24000, .den = 1001 }, .cdp_frame_rate = 0x1F }, |
| | 98 | { .frame_rate = { .num = 24, .den = 1 }, .cdp_frame_rate = 0x2F }, |
| | 99 | { .frame_rate = { .num = 25, .den = 1 }, .cdp_frame_rate = 0x3F }, |
| | 100 | { .frame_rate = { .num = 30000, .den = 1001 }, .cdp_frame_rate = 0x4F }, |
| | 101 | { .frame_rate = { .num = 30, .den = 1 }, .cdp_frame_rate = 0x5F }, |
| | 102 | { .frame_rate = { .num = 50, .den = 1 }, .cdp_frame_rate = 0x6F }, |
| | 103 | { .frame_rate = { .num = 60000, .den = 1001 }, .cdp_frame_rate = 0x7F }, |
| | 104 | { .frame_rate = { .num = 60, .den = 1 }, .cdp_frame_rate = 0x8F }, |
| | 105 | }; |
| | 106 | |
| | 107 | priv->cdp_frame_rate_byte = 0; |
| | 108 | |
| | 109 | for (int i = 0; i < FF_ARRAY_ELEMS(known_frame_rates); i++) { |
| | 110 | if (known_frame_rates[i].frame_rate.num == priv->cdp_frame_rate.num && known_frame_rates[i].frame_rate.den == priv->cdp_frame_rate.den) { |
| | 111 | priv->cdp_frame_rate_byte = known_frame_rates[i].cdp_frame_rate; |
| | 112 | break; |
| | 113 | } |
| | 114 | } |
| | 115 | |
| | 116 | if (priv->cdp_frame_rate_byte == 0) { |
| | 117 | av_log(ctx, |
| | 118 | AV_LOG_FATAL, |
| | 119 | "cdp_frame_rate not supported: %d/%d\n", |
| | 120 | priv->cdp_frame_rate.num, |
| | 121 | priv->cdp_frame_rate.den); |
| | 122 | return AVERROR(EINVAL); |
| | 123 | } |
| | 124 | |
| | 125 | return 0; |
| | 126 | } |
| | 127 | |
| | 128 | static int ff_eia608_to_smpte436m_filter(AVBSFContext *ctx, AVPacket *out) |
| | 129 | { |
| | 130 | EIA608ToSMPTE436MContext *priv = ctx->priv_data; |
| | 131 | AVPacket *in; |
| | 132 | |
| | 133 | int ret = ff_bsf_get_packet(ctx, &in); |
| | 134 | if (ret < 0) |
| | 135 | return ret; |
| | 136 | |
| | 137 | AVSmpte291mAnc8bit anc; |
| | 138 | anc.did = 0x61; |
| | 139 | anc.sdid_or_dbn = 0x1; |
| | 140 | |
| | 141 | uint8_t *p = anc.payload; |
| | 142 | |
| | 143 | *p++ = 0x96; // cdp_identifier -- always 0x9669 |
| | 144 | *p++ = 0x69; |
| | 145 | |
| | 146 | uint8_t *cdp_length_p = p++; |
| | 147 | |
| | 148 | *p++ = priv->cdp_frame_rate_byte; |
| | 149 | |
| | 150 | const uint8_t FLAG_CC_DATA_PRESENT = 0x40; |
| | 151 | const uint8_t FLAG_CAPTION_SERVICE_ACTIVE = 0x2; |
| | 152 | const uint8_t FLAG_RESERVED = 0x1; // must always be set |
| | 153 | |
| | 154 | *p++ = FLAG_CC_DATA_PRESENT | FLAG_CAPTION_SERVICE_ACTIVE | FLAG_RESERVED; |
| | 155 | |
| | 156 | AV_WB16(p, priv->cdp_sequence_cntr); |
| | 157 | p += 2; |
| | 158 | |
| | 159 | const uint8_t CC_DATA_SECTION_ID = 0x72; |
| | 160 | |
| | 161 | *p++ = CC_DATA_SECTION_ID; |
| | 162 | |
| | 163 | uint8_t *cc_count_p = p++; |
| | 164 | |
| | 165 | const uint8_t CC_COUNT_MASK = 0x1F; |
| | 166 | const int CDP_FOOTER_SIZE = 4; |
| | 167 | |
| | 168 | int cc_count = in->size / 3; |
| | 169 | int space_left = AV_SMPTE_291M_ANC_PAYLOAD_CAPACITY - (p - anc.payload); |
| | 170 | int cc_data_space_left = space_left - CDP_FOOTER_SIZE; |
| | 171 | int max_cc_count = FFMAX(cc_data_space_left / 3, CC_COUNT_MASK); |
| | 172 | |
| | 173 | if (cc_count > max_cc_count) { |
| | 174 | av_log(ctx, |
| | 175 | AV_LOG_ERROR, |
| | 176 | "cc_count (%d) is bigger than the maximum supported (%d), truncating captions packet\n", |
| | 177 | cc_count, |
| | 178 | max_cc_count); |
| | 179 | cc_count = max_cc_count; |
| | 180 | } |
| | 181 | |
| | 182 | *cc_count_p = cc_count | ~CC_COUNT_MASK; // other bits are reserved and set to ones |
| | 183 | |
| | 184 | for (size_t i = 0; i < cc_count; i++) { |
| | 185 | size_t start = i * 3; |
| | 186 | *p++ = in->data[start] | 0xF8; // fill reserved bits with ones |
| | 187 | *p++ = in->data[start + 1]; |
| | 188 | *p++ = in->data[start + 2]; |
| | 189 | } |
| | 190 | |
| | 191 | const uint8_t CDP_FOOTER_ID = 0x74; |
| | 192 | |
| | 193 | *p++ = CDP_FOOTER_ID; |
| | 194 | |
| | 195 | AV_WB16(p, priv->cdp_sequence_cntr); |
| | 196 | p += 2; |
| | 197 | |
| | 198 | uint8_t *packet_checksum_p = p; |
| | 199 | *p++ = 0; |
| | 200 | |
| | 201 | anc.data_count = p - anc.payload; |
| | 202 | *cdp_length_p = anc.data_count; |
| | 203 | |
| | 204 | int sum = 0; |
| | 205 | for (int i = 0; i < anc.data_count; i++) { |
| | 206 | sum += anc.payload[i]; |
| | 207 | } |
| | 208 | // set to an 8-bit value such that the sum of the bytes of the whole CDP mod 2^8 is 0 |
| | 209 | *packet_checksum_p = -sum; |
| | 210 | |
| | 211 | priv->cdp_sequence_cntr++; |
| | 212 | // cdp_sequence_cntr wraps around at 16-bits |
| | 213 | priv->cdp_sequence_cntr &= 0xFFFFU; |
| | 214 | |
| | 215 | av_smpte_291m_anc_8bit_fill_checksum(&anc); |
| | 216 | |
| | 217 | AVSmpte436mCodedAnc coded_anc; |
| | 218 | ret = av_smpte_291m_anc_8bit_encode( |
| | 219 | &coded_anc, priv->line_number, (AVSmpte436mWrappingType)priv->wrapping_type, priv->sample_coding, &anc, ctx); |
| | 220 | if (ret < 0) |
| | 221 | goto fail; |
| | 222 | |
| | 223 | ret = av_smpte_436m_anc_encode(NULL, 0, 1, &coded_anc); |
| | 224 | if (ret < 0) |
| | 225 | goto fail; |
| | 226 | |
| | 227 | ret = av_new_packet(out, ret); |
| | 228 | if (ret < 0) |
| | 229 | goto fail; |
| | 230 | |
| | 231 | ret = av_packet_copy_props(out, in); |
| | 232 | if (ret < 0) |
| | 233 | goto fail; |
| | 234 | |
| | 235 | ret = av_smpte_436m_anc_encode(out->data, out->size, 1, &coded_anc); |
| | 236 | if (ret < 0) |
| | 237 | goto fail; |
| | 238 | |
| | 239 | return 0; |
| | 240 | |
| | 241 | fail: |
| | 242 | if (ret < 0) |
| | 243 | av_packet_unref(out); |
| | 244 | av_packet_free(&in); |
| | 245 | return ret; |
| | 246 | } |
| | 247 | |
| | 248 | #define OFFSET(x) offsetof(EIA608ToSMPTE436MContext, x) |
| | 249 | #define FLAGS AV_OPT_FLAG_BSF_PARAM |
| | 250 | // clang-format off |
| | 251 | static const AVOption options[] = { |
| | 252 | { "line_number", "line number -- you probably want 9 or 11", OFFSET(line_number), AV_OPT_TYPE_UINT, { .i64 = 9 }, 0, 0xFFFF, FLAGS }, |
| | 253 | { "wrapping_type", "wrapping type", OFFSET(wrapping_type_opt), AV_OPT_TYPE_UINT, { .i64 = AV_SMPTE_436M_WRAPPING_TYPE_VANC_FRAME }, 0, 0xFF, FLAGS, .unit = "wrapping_type" }, |
| | 254 | FF_SMPTE_436M_WRAPPING_TYPE_VANC_AVOPTIONS(FLAGS, "wrapping_type"), |
| | 255 | { "sample_coding", "payload sample coding", OFFSET(sample_coding_opt), AV_OPT_TYPE_UINT, { .i64 = AV_SMPTE_436M_PAYLOAD_SAMPLE_CODING_8BIT_LUMA }, 0, 0xFF, FLAGS, .unit = "sample_coding" }, |
| | 256 | FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_ANC_AVOPTIONS(FLAGS, "sample_coding"), |
| | 257 | { "initial_cdp_sequence_cntr", "initial cdp_*_sequence_cntr value", OFFSET(cdp_sequence_cntr), AV_OPT_TYPE_UINT, { .i64 = 0 }, 0, 0xFFFF, FLAGS }, |
| | 258 | { "cdp_frame_rate", "set the `cdp_frame_rate` fields", OFFSET(cdp_frame_rate), AV_OPT_TYPE_VIDEO_RATE, { .str = "30000/1001" }, 0, INT_MAX, FLAGS }, |
| | 259 | { NULL }, |
| | 260 | }; |
| | 261 | // clang-format on |
| | 262 | |
| | 263 | static const AVClass eia608_to_smpte436m_class = { |
| | 264 | .class_name = "eia608_to_smpte436m bitstream filter", |
| | 265 | .item_name = av_default_item_name, |
| | 266 | .option = options, |
| | 267 | .version = LIBAVUTIL_VERSION_INT, |
| | 268 | }; |
| | 269 | |
| | 270 | const FFBitStreamFilter ff_eia608_to_smpte436m_bsf = { |
| | 271 | .p.name = "eia608_to_smpte436m", |
| | 272 | .p.codec_ids = (const enum AVCodecID[]){ AV_CODEC_ID_EIA_608, AV_CODEC_ID_NONE }, |
| | 273 | .p.priv_class = &eia608_to_smpte436m_class, |
| | 274 | .priv_data_size = sizeof(EIA608ToSMPTE436MContext), |
| | 275 | .init = ff_eia608_to_smpte436m_init, |
| | 276 | .filter = ff_eia608_to_smpte436m_filter, |
| | 277 | }; |