/src/ffmpeg/libavcodec/bsf/eia608_to_smpte436m.c
Line | Count | 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 | | static av_cold int ff_eia608_to_smpte436m_init(AVBSFContext *ctx) |
75 | 255 | { |
76 | 255 | EIA608ToSMPTE436MContext *priv = ctx->priv_data; |
77 | | |
78 | 255 | priv->wrapping_type = priv->wrapping_type_opt; |
79 | 255 | priv->sample_coding = priv->sample_coding_opt; |
80 | | |
81 | | // validate we can handle the selected wrapping type and sample coding |
82 | | |
83 | 255 | AVSmpte436mCodedAnc coded_anc; |
84 | | |
85 | 255 | int ret = av_smpte_291m_anc_8bit_encode( |
86 | 255 | &coded_anc, priv->line_number, priv->wrapping_type, priv->sample_coding, &test_anc, ctx); |
87 | 255 | if (ret < 0) |
88 | 0 | return ret; |
89 | | |
90 | 255 | ctx->par_out->codec_type = AVMEDIA_TYPE_DATA; |
91 | 255 | ctx->par_out->codec_id = AV_CODEC_ID_SMPTE_436M_ANC; |
92 | | |
93 | 255 | static const struct { |
94 | 255 | AVRational frame_rate; |
95 | 255 | uint8_t cdp_frame_rate; |
96 | 255 | } known_frame_rates[] = { |
97 | 255 | { .frame_rate = { .num = 24000, .den = 1001 }, .cdp_frame_rate = 0x1F }, |
98 | 255 | { .frame_rate = { .num = 24, .den = 1 }, .cdp_frame_rate = 0x2F }, |
99 | 255 | { .frame_rate = { .num = 25, .den = 1 }, .cdp_frame_rate = 0x3F }, |
100 | 255 | { .frame_rate = { .num = 30000, .den = 1001 }, .cdp_frame_rate = 0x4F }, |
101 | 255 | { .frame_rate = { .num = 30, .den = 1 }, .cdp_frame_rate = 0x5F }, |
102 | 255 | { .frame_rate = { .num = 50, .den = 1 }, .cdp_frame_rate = 0x6F }, |
103 | 255 | { .frame_rate = { .num = 60000, .den = 1001 }, .cdp_frame_rate = 0x7F }, |
104 | 255 | { .frame_rate = { .num = 60, .den = 1 }, .cdp_frame_rate = 0x8F }, |
105 | 255 | }; |
106 | | |
107 | 255 | priv->cdp_frame_rate_byte = 0; |
108 | | |
109 | 1.02k | for (int i = 0; i < FF_ARRAY_ELEMS(known_frame_rates); i++) { |
110 | 1.02k | 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 | 255 | priv->cdp_frame_rate_byte = known_frame_rates[i].cdp_frame_rate; |
112 | 255 | break; |
113 | 255 | } |
114 | 1.02k | } |
115 | | |
116 | 255 | if (priv->cdp_frame_rate_byte == 0) { |
117 | 0 | av_log(ctx, |
118 | 0 | AV_LOG_FATAL, |
119 | 0 | "cdp_frame_rate not supported: %d/%d\n", |
120 | 0 | priv->cdp_frame_rate.num, |
121 | 0 | priv->cdp_frame_rate.den); |
122 | 0 | return AVERROR(EINVAL); |
123 | 0 | } |
124 | | |
125 | 255 | return 0; |
126 | 255 | } |
127 | | |
128 | | static int ff_eia608_to_smpte436m_filter(AVBSFContext *ctx, AVPacket *out) |
129 | 23.5k | { |
130 | 23.5k | EIA608ToSMPTE436MContext *priv = ctx->priv_data; |
131 | 23.5k | AVPacket *in; |
132 | | |
133 | 23.5k | int ret = ff_bsf_get_packet(ctx, &in); |
134 | 23.5k | if (ret < 0) |
135 | 11.8k | return ret; |
136 | | |
137 | 11.6k | AVSmpte291mAnc8bit anc; |
138 | 11.6k | anc.did = 0x61; |
139 | 11.6k | anc.sdid_or_dbn = 0x1; |
140 | | |
141 | 11.6k | uint8_t *p = anc.payload; |
142 | | |
143 | 11.6k | *p++ = 0x96; // cdp_identifier -- always 0x9669 |
144 | 11.6k | *p++ = 0x69; |
145 | | |
146 | 11.6k | uint8_t *cdp_length_p = p++; |
147 | | |
148 | 11.6k | *p++ = priv->cdp_frame_rate_byte; |
149 | | |
150 | 11.6k | const uint8_t FLAG_CC_DATA_PRESENT = 0x40; |
151 | 11.6k | const uint8_t FLAG_CAPTION_SERVICE_ACTIVE = 0x2; |
152 | 11.6k | const uint8_t FLAG_RESERVED = 0x1; // must always be set |
153 | | |
154 | 11.6k | *p++ = FLAG_CC_DATA_PRESENT | FLAG_CAPTION_SERVICE_ACTIVE | FLAG_RESERVED; |
155 | | |
156 | 11.6k | AV_WB16(p, priv->cdp_sequence_cntr); |
157 | 11.6k | p += 2; |
158 | | |
159 | 11.6k | const uint8_t CC_DATA_SECTION_ID = 0x72; |
160 | | |
161 | 11.6k | *p++ = CC_DATA_SECTION_ID; |
162 | | |
163 | 11.6k | uint8_t *cc_count_p = p++; |
164 | | |
165 | 11.6k | const uint8_t CC_COUNT_MASK = 0x1F; |
166 | 11.6k | const int CDP_FOOTER_SIZE = 4; |
167 | | |
168 | 11.6k | int cc_count = in->size / 3; |
169 | 11.6k | int space_left = AV_SMPTE_291M_ANC_PAYLOAD_CAPACITY - (p - anc.payload); |
170 | 11.6k | int cc_data_space_left = space_left - CDP_FOOTER_SIZE; |
171 | 11.6k | int max_cc_count = FFMAX(cc_data_space_left / 3, CC_COUNT_MASK); |
172 | | |
173 | 11.6k | if (cc_count > max_cc_count) { |
174 | 1.00k | av_log(ctx, |
175 | 1.00k | AV_LOG_ERROR, |
176 | 1.00k | "cc_count (%d) is bigger than the maximum supported (%d), truncating captions packet\n", |
177 | 1.00k | cc_count, |
178 | 1.00k | max_cc_count); |
179 | 1.00k | cc_count = max_cc_count; |
180 | 1.00k | } |
181 | | |
182 | 11.6k | *cc_count_p = cc_count | ~CC_COUNT_MASK; // other bits are reserved and set to ones |
183 | | |
184 | 135k | for (size_t i = 0; i < cc_count; i++) { |
185 | 124k | size_t start = i * 3; |
186 | 124k | *p++ = in->data[start] | 0xF8; // fill reserved bits with ones |
187 | 124k | *p++ = in->data[start + 1]; |
188 | 124k | *p++ = in->data[start + 2]; |
189 | 124k | } |
190 | | |
191 | 11.6k | const uint8_t CDP_FOOTER_ID = 0x74; |
192 | | |
193 | 11.6k | *p++ = CDP_FOOTER_ID; |
194 | | |
195 | 11.6k | AV_WB16(p, priv->cdp_sequence_cntr); |
196 | 11.6k | p += 2; |
197 | | |
198 | 11.6k | uint8_t *packet_checksum_p = p; |
199 | 11.6k | *p++ = 0; |
200 | | |
201 | 11.6k | anc.data_count = p - anc.payload; |
202 | 11.6k | *cdp_length_p = anc.data_count; |
203 | | |
204 | 11.6k | int sum = 0; |
205 | 535k | for (int i = 0; i < anc.data_count; i++) { |
206 | 523k | sum += anc.payload[i]; |
207 | 523k | } |
208 | | // set to an 8-bit value such that the sum of the bytes of the whole CDP mod 2^8 is 0 |
209 | 11.6k | *packet_checksum_p = -sum; |
210 | | |
211 | 11.6k | priv->cdp_sequence_cntr++; |
212 | | // cdp_sequence_cntr wraps around at 16-bits |
213 | 11.6k | priv->cdp_sequence_cntr &= 0xFFFFU; |
214 | | |
215 | 11.6k | av_smpte_291m_anc_8bit_fill_checksum(&anc); |
216 | | |
217 | 11.6k | AVSmpte436mCodedAnc coded_anc; |
218 | 11.6k | ret = av_smpte_291m_anc_8bit_encode( |
219 | 11.6k | &coded_anc, priv->line_number, (AVSmpte436mWrappingType)priv->wrapping_type, priv->sample_coding, &anc, ctx); |
220 | 11.6k | if (ret < 0) |
221 | 0 | goto fail; |
222 | | |
223 | 11.6k | ret = av_smpte_436m_anc_encode(NULL, 0, 1, &coded_anc); |
224 | 11.6k | if (ret < 0) |
225 | 0 | goto fail; |
226 | | |
227 | 11.6k | ret = av_new_packet(out, ret); |
228 | 11.6k | if (ret < 0) |
229 | 0 | goto fail; |
230 | | |
231 | 11.6k | ret = av_packet_copy_props(out, in); |
232 | 11.6k | if (ret < 0) |
233 | 0 | goto fail; |
234 | | |
235 | 11.6k | ret = av_smpte_436m_anc_encode(out->data, out->size, 1, &coded_anc); |
236 | 11.6k | if (ret < 0) |
237 | 0 | goto fail; |
238 | | |
239 | 11.6k | ret = 0; |
240 | | |
241 | 11.6k | fail: |
242 | 11.6k | if (ret < 0) |
243 | 0 | av_packet_unref(out); |
244 | 11.6k | av_packet_free(&in); |
245 | 11.6k | return ret; |
246 | 11.6k | } |
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 | | }; |