1
0
mirror of https://github.com/FFmpeg/FFmpeg.git synced 2025-08-10 06:10:52 +02:00

lavc: add eia608_to_smpte436m bitstream filter

Signed-off-by: Jacob Lifshay <programmerjake@gmail.com>
This commit is contained in:
Jacob Lifshay
2025-07-22 23:21:13 -07:00
parent aa5b9db7bd
commit 0271d0423a
5 changed files with 326 additions and 0 deletions

1
configure vendored
View File

@@ -3531,6 +3531,7 @@ av1_metadata_bsf_select="cbs_av1"
dovi_rpu_bsf_select="cbs_h265 cbs_av1 dovi_rpudec dovi_rpuenc"
dts2pts_bsf_select="cbs_h264 h264parse"
eac3_core_bsf_select="ac3_parser"
eia608_to_smpte436m_bsf_select="smpte_436m"
evc_frame_merge_bsf_select="evcparse"
filter_units_bsf_select="cbs"
h264_metadata_bsf_deps="const_nan"

View File

@@ -189,6 +189,52 @@ see page 44-46 or section 5.5 of
Extract the core from a E-AC-3 stream, dropping extra channels.
@section eia608_to_smpte436m
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.
@table @option
@item line_number
Choose which line number the generated VANC packets should go on. You generally want either line 9 (the default) or 11.
@item wrapping_type
Choose the SMPTE 436M wrapping type, defaults to @samp{vanc_frame}.
It accepts the values:
@table @samp
@item vanc_frame
VANC frame (interlaced or segmented progressive frame)
@item vanc_field_1
@item vanc_field_2
@item vanc_progressive_frame
@end table
@item sample_coding
Choose the SMPTE 436M sample coding, defaults to @samp{8bit_luma}.
It accepts the values:
@table @samp
@item 8bit_luma
8-bit component luma samples
@item 8bit_color_diff
8-bit component color difference samples
@item 8bit_luma_and_color_diff
8-bit component luma and color difference samples
@item 10bit_luma
10-bit component luma samples
@item 10bit_color_diff
10-bit component color difference samples
@item 10bit_luma_and_color_diff
10-bit component luma and color difference samples
@item 8bit_luma_parity_error
8-bit component luma samples with parity error
@item 8bit_color_diff_parity_error
8-bit component color difference samples with parity error
@item 8bit_luma_and_color_diff_parity_error
8-bit component luma and color difference samples with parity error
@end table
@item initial_cdp_sequence_cntr
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.
@item cdp_frame_rate
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}.
@end table
@section extract_extradata
Extract the in-band extradata.

View File

@@ -36,6 +36,7 @@ extern const FFBitStreamFilter ff_dovi_rpu_bsf;
extern const FFBitStreamFilter ff_dts2pts_bsf;
extern const FFBitStreamFilter ff_dv_error_marker_bsf;
extern const FFBitStreamFilter ff_eac3_core_bsf;
extern const FFBitStreamFilter ff_eia608_to_smpte436m_bsf;
extern const FFBitStreamFilter ff_evc_frame_merge_bsf;
extern const FFBitStreamFilter ff_extract_extradata_bsf;
extern const FFBitStreamFilter ff_filter_units_bsf;

View File

@@ -12,6 +12,7 @@ OBJS-$(CONFIG_DTS2PTS_BSF) += bsf/dts2pts.o
OBJS-$(CONFIG_DUMP_EXTRADATA_BSF) += bsf/dump_extradata.o
OBJS-$(CONFIG_DV_ERROR_MARKER_BSF) += bsf/dv_error_marker.o
OBJS-$(CONFIG_EAC3_CORE_BSF) += bsf/eac3_core.o
OBJS-$(CONFIG_EIA608_TO_SMPTE436M_BSF) += bsf/eia608_to_smpte436m.o
OBJS-$(CONFIG_EVC_FRAME_MERGE_BSF) += bsf/evc_frame_merge.o
OBJS-$(CONFIG_EXTRACT_EXTRADATA_BSF) += bsf/extract_extradata.o
OBJS-$(CONFIG_FILTER_UNITS_BSF) += bsf/filter_units.o

View File

@@ -0,0 +1,277 @@
/*
* EIA-608 to MXF SMPTE-436M ANC bitstream filter
* Copyright (c) 2025 Jacob Lifshay
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "bsf.h"
#include "bsf_internal.h"
#include "codec_id.h"
#include "libavcodec/smpte_436m.h"
#include "libavcodec/smpte_436m_internal.h"
#include "libavutil/avassert.h"
#include "libavutil/avutil.h"
#include "libavutil/error.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/macros.h"
#include "libavutil/opt.h"
#include "libavutil/rational.h"
typedef struct EIA608ToSMPTE436MContext {
const AVClass *class;
unsigned line_number;
unsigned cdp_sequence_cntr;
unsigned wrapping_type_opt;
unsigned sample_coding_opt;
AVSmpte436mWrappingType wrapping_type;
AVSmpte436mPayloadSampleCoding sample_coding;
AVRational cdp_frame_rate;
uint8_t cdp_frame_rate_byte;
} EIA608ToSMPTE436MContext;
// clang-format off
static const AVSmpte291mAnc8bit test_anc = {
.did = 0x61,
.sdid_or_dbn = 0x01,
.data_count = 0x49,
.payload = {
// header
0x96, 0x69, 0x49, 0x7F, 0x43, 0xFA, 0x8D, 0x72, 0xF4,
// 608 triples
0xFC, 0x80, 0x80, 0xFD, 0x80, 0x80,
// 708 padding
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
// footer
0x74, 0xFA, 0x8D, 0x81,
},
.checksum = 0xAB,
};
// clang-format on
static av_cold int ff_eia608_to_smpte436m_init(AVBSFContext *ctx)
{
EIA608ToSMPTE436MContext *priv = ctx->priv_data;
priv->wrapping_type = priv->wrapping_type_opt;
priv->sample_coding = priv->sample_coding_opt;
// validate we can handle the selected wrapping type and sample coding
AVSmpte436mCodedAnc coded_anc;
int ret = av_smpte_291m_anc_8bit_encode(
&coded_anc, priv->line_number, priv->wrapping_type, priv->sample_coding, &test_anc, ctx);
if (ret < 0)
return ret;
ctx->par_out->codec_type = AVMEDIA_TYPE_DATA;
ctx->par_out->codec_id = AV_CODEC_ID_SMPTE_436M_ANC;
static const struct {
AVRational frame_rate;
uint8_t cdp_frame_rate;
} known_frame_rates[] = {
{ .frame_rate = { .num = 24000, .den = 1001 }, .cdp_frame_rate = 0x1F },
{ .frame_rate = { .num = 24, .den = 1 }, .cdp_frame_rate = 0x2F },
{ .frame_rate = { .num = 25, .den = 1 }, .cdp_frame_rate = 0x3F },
{ .frame_rate = { .num = 30000, .den = 1001 }, .cdp_frame_rate = 0x4F },
{ .frame_rate = { .num = 30, .den = 1 }, .cdp_frame_rate = 0x5F },
{ .frame_rate = { .num = 50, .den = 1 }, .cdp_frame_rate = 0x6F },
{ .frame_rate = { .num = 60000, .den = 1001 }, .cdp_frame_rate = 0x7F },
{ .frame_rate = { .num = 60, .den = 1 }, .cdp_frame_rate = 0x8F },
};
priv->cdp_frame_rate_byte = 0;
for (int i = 0; i < FF_ARRAY_ELEMS(known_frame_rates); i++) {
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) {
priv->cdp_frame_rate_byte = known_frame_rates[i].cdp_frame_rate;
break;
}
}
if (priv->cdp_frame_rate_byte == 0) {
av_log(ctx,
AV_LOG_FATAL,
"cdp_frame_rate not supported: %d/%d\n",
priv->cdp_frame_rate.num,
priv->cdp_frame_rate.den);
return AVERROR(EINVAL);
}
return 0;
}
static int ff_eia608_to_smpte436m_filter(AVBSFContext *ctx, AVPacket *out)
{
EIA608ToSMPTE436MContext *priv = ctx->priv_data;
AVPacket *in;
int ret = ff_bsf_get_packet(ctx, &in);
if (ret < 0)
return ret;
AVSmpte291mAnc8bit anc;
anc.did = 0x61;
anc.sdid_or_dbn = 0x1;
uint8_t *p = anc.payload;
*p++ = 0x96; // cdp_identifier -- always 0x9669
*p++ = 0x69;
uint8_t *cdp_length_p = p++;
*p++ = priv->cdp_frame_rate_byte;
const uint8_t FLAG_CC_DATA_PRESENT = 0x40;
const uint8_t FLAG_CAPTION_SERVICE_ACTIVE = 0x2;
const uint8_t FLAG_RESERVED = 0x1; // must always be set
*p++ = FLAG_CC_DATA_PRESENT | FLAG_CAPTION_SERVICE_ACTIVE | FLAG_RESERVED;
AV_WB16(p, priv->cdp_sequence_cntr);
p += 2;
const uint8_t CC_DATA_SECTION_ID = 0x72;
*p++ = CC_DATA_SECTION_ID;
uint8_t *cc_count_p = p++;
const uint8_t CC_COUNT_MASK = 0x1F;
const int CDP_FOOTER_SIZE = 4;
int cc_count = in->size / 3;
int space_left = AV_SMPTE_291M_ANC_PAYLOAD_CAPACITY - (p - anc.payload);
int cc_data_space_left = space_left - CDP_FOOTER_SIZE;
int max_cc_count = FFMAX(cc_data_space_left / 3, CC_COUNT_MASK);
if (cc_count > max_cc_count) {
av_log(ctx,
AV_LOG_ERROR,
"cc_count (%d) is bigger than the maximum supported (%d), truncating captions packet\n",
cc_count,
max_cc_count);
cc_count = max_cc_count;
}
*cc_count_p = cc_count | ~CC_COUNT_MASK; // other bits are reserved and set to ones
for (size_t i = 0; i < cc_count; i++) {
size_t start = i * 3;
*p++ = in->data[start] | 0xF8; // fill reserved bits with ones
*p++ = in->data[start + 1];
*p++ = in->data[start + 2];
}
const uint8_t CDP_FOOTER_ID = 0x74;
*p++ = CDP_FOOTER_ID;
AV_WB16(p, priv->cdp_sequence_cntr);
p += 2;
uint8_t *packet_checksum_p = p;
*p++ = 0;
anc.data_count = p - anc.payload;
*cdp_length_p = anc.data_count;
int sum = 0;
for (int i = 0; i < anc.data_count; i++) {
sum += anc.payload[i];
}
// set to an 8-bit value such that the sum of the bytes of the whole CDP mod 2^8 is 0
*packet_checksum_p = -sum;
priv->cdp_sequence_cntr++;
// cdp_sequence_cntr wraps around at 16-bits
priv->cdp_sequence_cntr &= 0xFFFFU;
av_smpte_291m_anc_8bit_fill_checksum(&anc);
AVSmpte436mCodedAnc coded_anc;
ret = av_smpte_291m_anc_8bit_encode(
&coded_anc, priv->line_number, (AVSmpte436mWrappingType)priv->wrapping_type, priv->sample_coding, &anc, ctx);
if (ret < 0)
goto fail;
ret = av_smpte_436m_anc_encode(NULL, 0, 1, &coded_anc);
if (ret < 0)
goto fail;
ret = av_new_packet(out, ret);
if (ret < 0)
goto fail;
ret = av_packet_copy_props(out, in);
if (ret < 0)
goto fail;
ret = av_smpte_436m_anc_encode(out->data, out->size, 1, &coded_anc);
if (ret < 0)
goto fail;
return 0;
fail:
if (ret < 0)
av_packet_unref(out);
av_packet_free(&in);
return ret;
}
#define OFFSET(x) offsetof(EIA608ToSMPTE436MContext, x)
#define FLAGS AV_OPT_FLAG_BSF_PARAM
// clang-format off
static const AVOption options[] = {
{ "line_number", "line number -- you probably want 9 or 11", OFFSET(line_number), AV_OPT_TYPE_UINT, { .i64 = 9 }, 0, 0xFFFF, FLAGS },
{ "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" },
FF_SMPTE_436M_WRAPPING_TYPE_VANC_AVOPTIONS(FLAGS, "wrapping_type"),
{ "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" },
FF_SMPTE_436M_PAYLOAD_SAMPLE_CODING_ANC_AVOPTIONS(FLAGS, "sample_coding"),
{ "initial_cdp_sequence_cntr", "initial cdp_*_sequence_cntr value", OFFSET(cdp_sequence_cntr), AV_OPT_TYPE_UINT, { .i64 = 0 }, 0, 0xFFFF, FLAGS },
{ "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 },
{ NULL },
};
// clang-format on
static const AVClass eia608_to_smpte436m_class = {
.class_name = "eia608_to_smpte436m bitstream filter",
.item_name = av_default_item_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
};
const FFBitStreamFilter ff_eia608_to_smpte436m_bsf = {
.p.name = "eia608_to_smpte436m",
.p.codec_ids = (const enum AVCodecID[]){ AV_CODEC_ID_EIA_608, AV_CODEC_ID_NONE },
.p.priv_class = &eia608_to_smpte436m_class,
.priv_data_size = sizeof(EIA608ToSMPTE436MContext),
.init = ff_eia608_to_smpte436m_init,
.filter = ff_eia608_to_smpte436m_filter,
};