FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavcodec/bsf/eia608_to_smpte436m.c
Date: 2025-08-19 23:55:23
Exec Total Coverage
Lines: 81 92 88.0%
Functions: 2 2 100.0%
Branches: 19 30 63.3%

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