History

lavc: add eia608_to_smpte436m bitstream filter

Jacob Lifshay committed 1 year ago

Commit 0271d041 parent aa5b9db5 files · +326 −0Browse files

Unified · Split

5 files
  1. configure +1 −0
  2. doc/bitstream_filters.texi +46 −0
  3. libavcodec/bitstream_filters.c +1 −0
  4. libavcodec/bsf/Makefile +1 −0
  5. libavcodec/bsf/eia608_to_smpte436m.c +277 −0
configure+1−0
@@ -3531,6 +3531,7 @@ av1_metadata_bsf_select="cbs_av1" · 3530 unmodified lines
35313531 dovi_rpu_bsf_select="cbs_h265 cbs_av1 dovi_rpudec dovi_rpuenc"
35323532 dts2pts_bsf_select="cbs_h264 h264parse"
35333533 eac3_core_bsf_select="ac3_parser"
3534+eia608_to_smpte436m_bsf_select="smpte_436m"
35343535 evc_frame_merge_bsf_select="evcparse"
35353536 filter_units_bsf_select="cbs"
35363537 h264_metadata_bsf_deps="const_nan"
doc/bitstream_filters.texi+46−0
@@ -189,6 +189,52 @@ see page 44-46 or section 5.5 of · 188 unmodified lines
189189
190190 Extract the core from a E-AC-3 stream, dropping extra channels.
191191
192+@section eia608_to_smpte436m
193+
194+Convert from a @code{EIA_608} stream to a @code{SMPTE_436M_ANC} data stream, wrapping the closed captions in CTA-708 CDP VANC packets.
195+
196+@table @option
197+@item line_number
198+Choose which line number the generated VANC packets should go on. You generally want either line 9 (the default) or 11.
199+@item wrapping_type
200+Choose the SMPTE 436M wrapping type, defaults to @samp{vanc_frame}.
201+It accepts the values:
202+@table @samp
203+@item vanc_frame
204+VANC frame (interlaced or segmented progressive frame)
205+@item vanc_field_1
206+@item vanc_field_2
207+@item vanc_progressive_frame
208+@end table
209+@item sample_coding
210+Choose the SMPTE 436M sample coding, defaults to @samp{8bit_luma}.
211+It accepts the values:
212+@table @samp
213+@item 8bit_luma
214+8-bit component luma samples
215+@item 8bit_color_diff
216+8-bit component color difference samples
217+@item 8bit_luma_and_color_diff
218+8-bit component luma and color difference samples
219+@item 10bit_luma
220+10-bit component luma samples
221+@item 10bit_color_diff
222+10-bit component color difference samples
223+@item 10bit_luma_and_color_diff
224+10-bit component luma and color difference samples
225+@item 8bit_luma_parity_error
226+8-bit component luma samples with parity error
227+@item 8bit_color_diff_parity_error
228+8-bit component color difference samples with parity error
229+@item 8bit_luma_and_color_diff_parity_error
230+8-bit component luma and color difference samples with parity error
231+@end table
232+@item initial_cdp_sequence_cntr
233+The initial value of the CDP's 16-bit unsigned integer @code{cdp_hdr_sequence_cntr} and @code{cdp_ftr_sequence_cntr} fields. Defaults to 0.
234+@item cdp_frame_rate
235+Set the CDP's @code{cdp_frame_rate} field. This doesn't actually change the timing of the data stream, it just changes the values inserted in that field in the generated CDP packets. Defaults to @samp{30000/1001}.
236+@end table
237+
192238 @section extract_extradata
193239
194240 Extract the in-band extradata.
libavcodec/bitstream_filters.c+1−0
@@ -36,6 +36,7 @@ extern const FFBitStreamFilter ff_dovi_rpu_bsf; · 35 unmodified lines
3636 extern const FFBitStreamFilter ff_dts2pts_bsf;
3737 extern const FFBitStreamFilter ff_dv_error_marker_bsf;
3838 extern const FFBitStreamFilter ff_eac3_core_bsf;
39+extern const FFBitStreamFilter ff_eia608_to_smpte436m_bsf;
3940 extern const FFBitStreamFilter ff_evc_frame_merge_bsf;
4041 extern const FFBitStreamFilter ff_extract_extradata_bsf;
4142 extern const FFBitStreamFilter ff_filter_units_bsf;
libavcodec/bsf/Makefile+1−0
@@ -12,6 +12,7 @@ OBJS-$(CONFIG_DTS2PTS_BSF) += bsf/dts2pts.o · 11 unmodified lines
1212 OBJS-$(CONFIG_DUMP_EXTRADATA_BSF) += bsf/dump_extradata.o
1313 OBJS-$(CONFIG_DV_ERROR_MARKER_BSF) += bsf/dv_error_marker.o
1414 OBJS-$(CONFIG_EAC3_CORE_BSF) += bsf/eac3_core.o
15+OBJS-$(CONFIG_EIA608_TO_SMPTE436M_BSF) += bsf/eia608_to_smpte436m.o
1516 OBJS-$(CONFIG_EVC_FRAME_MERGE_BSF) += bsf/evc_frame_merge.o
1617 OBJS-$(CONFIG_EXTRACT_EXTRADATA_BSF) += bsf/extract_extradata.o
1718 OBJS-$(CONFIG_FILTER_UNITS_BSF) += bsf/filter_units.o
libavcodec/bsf/eia608_to_smpte436m.c+277−0
@@ -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+};

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t