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 |