reported in https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20024#issuecomment-2405 Reported-by: Sean McGovern <gseanmcg@gmail.com> Signed-off-by: Jacob Lifshay <programmerjake@gmail.com>
278 lines
9.4 KiB
C
278 lines
9.4 KiB
C
/*
|
|
* 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;
|
|
|
|
ret = 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,
|
|
};
|