| Line | Branch | Exec | Source |
|---|---|---|---|
| 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 | 4 | static av_cold int ff_eia608_to_smpte436m_init(AVBSFContext *ctx) | |
| 75 | { | ||
| 76 | 4 | EIA608ToSMPTE436MContext *priv = ctx->priv_data; | |
| 77 | |||
| 78 | 4 | priv->wrapping_type = priv->wrapping_type_opt; | |
| 79 | 4 | 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 | 4 | int ret = av_smpte_291m_anc_8bit_encode( | |
| 86 | 4 | &coded_anc, priv->line_number, priv->wrapping_type, priv->sample_coding, &test_anc, ctx); | |
| 87 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | if (ret < 0) |
| 88 | ✗ | return ret; | |
| 89 | |||
| 90 | 4 | ctx->par_out->codec_type = AVMEDIA_TYPE_DATA; | |
| 91 | 4 | 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 | 4 | priv->cdp_frame_rate_byte = 0; | |
| 108 | |||
| 109 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | for (int i = 0; i < FF_ARRAY_ELEMS(known_frame_rates); i++) { |
| 110 |
3/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
19 | 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 | 4 | priv->cdp_frame_rate_byte = known_frame_rates[i].cdp_frame_rate; | |
| 112 | 4 | break; | |
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
|
4 | 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 | 4 | return 0; | |
| 126 | } | ||
| 127 | |||
| 128 | 600 | static int ff_eia608_to_smpte436m_filter(AVBSFContext *ctx, AVPacket *out) | |
| 129 | { | ||
| 130 | 600 | EIA608ToSMPTE436MContext *priv = ctx->priv_data; | |
| 131 | AVPacket *in; | ||
| 132 | |||
| 133 | 600 | int ret = ff_bsf_get_packet(ctx, &in); | |
| 134 |
2/2✓ Branch 0 taken 302 times.
✓ Branch 1 taken 298 times.
|
600 | if (ret < 0) |
| 135 | 302 | return ret; | |
| 136 | |||
| 137 | AVSmpte291mAnc8bit anc; | ||
| 138 | 298 | anc.did = 0x61; | |
| 139 | 298 | anc.sdid_or_dbn = 0x1; | |
| 140 | |||
| 141 | 298 | uint8_t *p = anc.payload; | |
| 142 | |||
| 143 | 298 | *p++ = 0x96; // cdp_identifier -- always 0x9669 | |
| 144 | 298 | *p++ = 0x69; | |
| 145 | |||
| 146 | 298 | uint8_t *cdp_length_p = p++; | |
| 147 | |||
| 148 | 298 | *p++ = priv->cdp_frame_rate_byte; | |
| 149 | |||
| 150 | 298 | const uint8_t FLAG_CC_DATA_PRESENT = 0x40; | |
| 151 | 298 | const uint8_t FLAG_CAPTION_SERVICE_ACTIVE = 0x2; | |
| 152 | 298 | const uint8_t FLAG_RESERVED = 0x1; // must always be set | |
| 153 | |||
| 154 | 298 | *p++ = FLAG_CC_DATA_PRESENT | FLAG_CAPTION_SERVICE_ACTIVE | FLAG_RESERVED; | |
| 155 | |||
| 156 | 298 | AV_WB16(p, priv->cdp_sequence_cntr); | |
| 157 | 298 | p += 2; | |
| 158 | |||
| 159 | 298 | const uint8_t CC_DATA_SECTION_ID = 0x72; | |
| 160 | |||
| 161 | 298 | *p++ = CC_DATA_SECTION_ID; | |
| 162 | |||
| 163 | 298 | uint8_t *cc_count_p = p++; | |
| 164 | |||
| 165 | 298 | const uint8_t CC_COUNT_MASK = 0x1F; | |
| 166 | 298 | const int CDP_FOOTER_SIZE = 4; | |
| 167 | |||
| 168 | 298 | int cc_count = in->size / 3; | |
| 169 | 298 | int space_left = AV_SMPTE_291M_ANC_PAYLOAD_CAPACITY - (p - anc.payload); | |
| 170 | 298 | int cc_data_space_left = space_left - CDP_FOOTER_SIZE; | |
| 171 | 298 | int max_cc_count = FFMAX(cc_data_space_left / 3, CC_COUNT_MASK); | |
| 172 | |||
| 173 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 298 times.
|
298 | 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 | 298 | *cc_count_p = cc_count | ~CC_COUNT_MASK; // other bits are reserved and set to ones | |
| 183 | |||
| 184 |
2/2✓ Branch 0 taken 2328 times.
✓ Branch 1 taken 298 times.
|
2626 | for (size_t i = 0; i < cc_count; i++) { |
| 185 | 2328 | size_t start = i * 3; | |
| 186 | 2328 | *p++ = in->data[start] | 0xF8; // fill reserved bits with ones | |
| 187 | 2328 | *p++ = in->data[start + 1]; | |
| 188 | 2328 | *p++ = in->data[start + 2]; | |
| 189 | } | ||
| 190 | |||
| 191 | 298 | const uint8_t CDP_FOOTER_ID = 0x74; | |
| 192 | |||
| 193 | 298 | *p++ = CDP_FOOTER_ID; | |
| 194 | |||
| 195 | 298 | AV_WB16(p, priv->cdp_sequence_cntr); | |
| 196 | 298 | p += 2; | |
| 197 | |||
| 198 | 298 | uint8_t *packet_checksum_p = p; | |
| 199 | 298 | *p++ = 0; | |
| 200 | |||
| 201 | 298 | anc.data_count = p - anc.payload; | |
| 202 | 298 | *cdp_length_p = anc.data_count; | |
| 203 | |||
| 204 | 298 | int sum = 0; | |
| 205 |
2/2✓ Branch 0 taken 10858 times.
✓ Branch 1 taken 298 times.
|
11156 | for (int i = 0; i < anc.data_count; i++) { |
| 206 | 10858 | 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 | 298 | *packet_checksum_p = -sum; | |
| 210 | |||
| 211 | 298 | priv->cdp_sequence_cntr++; | |
| 212 | // cdp_sequence_cntr wraps around at 16-bits | ||
| 213 | 298 | priv->cdp_sequence_cntr &= 0xFFFFU; | |
| 214 | |||
| 215 | 298 | av_smpte_291m_anc_8bit_fill_checksum(&anc); | |
| 216 | |||
| 217 | AVSmpte436mCodedAnc coded_anc; | ||
| 218 | 298 | ret = av_smpte_291m_anc_8bit_encode( | |
| 219 | 298 | &coded_anc, priv->line_number, (AVSmpte436mWrappingType)priv->wrapping_type, priv->sample_coding, &anc, ctx); | |
| 220 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 298 times.
|
298 | if (ret < 0) |
| 221 | ✗ | goto fail; | |
| 222 | |||
| 223 | 298 | ret = av_smpte_436m_anc_encode(NULL, 0, 1, &coded_anc); | |
| 224 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 298 times.
|
298 | if (ret < 0) |
| 225 | ✗ | goto fail; | |
| 226 | |||
| 227 | 298 | ret = av_new_packet(out, ret); | |
| 228 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 298 times.
|
298 | if (ret < 0) |
| 229 | ✗ | goto fail; | |
| 230 | |||
| 231 | 298 | ret = av_packet_copy_props(out, in); | |
| 232 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 298 times.
|
298 | if (ret < 0) |
| 233 | ✗ | goto fail; | |
| 234 | |||
| 235 | 298 | ret = av_smpte_436m_anc_encode(out->data, out->size, 1, &coded_anc); | |
| 236 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 298 times.
|
298 | if (ret < 0) |
| 237 | ✗ | goto fail; | |
| 238 | |||
| 239 | 298 | ret = 0; | |
| 240 | |||
| 241 | 298 | fail: | |
| 242 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 298 times.
|
298 | if (ret < 0) |
| 243 | ✗ | av_packet_unref(out); | |
| 244 | 298 | av_packet_free(&in); | |
| 245 | 298 | 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 | }; | ||
| 278 |