[FFmpeg-devel] [PATCH 3/4] avdevice/decklink_dec: Added Closed caption decode from VANC
kjeyapal at akamai.com
kjeyapal at akamai.com
Thu Aug 31 11:36:49 EEST 2017
From: Karthick J <kjeyapal at akamai.com>
Signed-off-by: Karthick J <kjeyapal at akamai.com>
---
libavcodec/avcodec.h | 7 ++
libavdevice/decklink_dec.cpp | 167 ++++++++++++++++++++++++++++++++++++++++---
2 files changed, 165 insertions(+), 9 deletions(-)
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index 513236a..d4a458f 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -1600,6 +1600,13 @@ enum AVPacketSideDataType {
AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
/**
+ * ATSC A53 Part 4 Closed Captions. This metadata should be associated with
+ * a video stream. A53 CC bitstream is stored as uint8_t in AVPacketSideData.data.
+ * The number of bytes of CC data is AVPacketSideData.size.
+ */
+ AV_PKT_DATA_A53_CC,
+
+ /**
* The number of side data elements (in fact a bit more than it).
* This is not part of the public API/ABI in the sense that it may
* change when new side data types are added.
diff --git a/libavdevice/decklink_dec.cpp b/libavdevice/decklink_dec.cpp
index 0b88fc8..23e1b35 100644
--- a/libavdevice/decklink_dec.cpp
+++ b/libavdevice/decklink_dec.cpp
@@ -106,6 +106,13 @@ static void get_vanc_lines(int bmd_field_dominance, int height, int *field0_vanc
}
}
+static inline unsigned parity (unsigned x)
+{
+ for (unsigned i = 4 * sizeof (x); i > 0; i /= 2)
+ x ^= x >> i;
+ return x & 1;
+}
+
/* The 10-bit VANC data is packed in V210, we only need the luma component. */
static void extract_luma_from_v210(uint16_t *dst, const uint8_t *src, int width)
{
@@ -249,6 +256,152 @@ static uint8_t* teletext_data_unit_from_vanc_data(uint16_t *py, uint8_t *tgt, in
return tgt;
}
+uint8_t *vanc_to_cc(AVFormatContext *avctx, uint16_t *buf, size_t words,
+ unsigned &cc_count)
+{
+ size_t len = (buf[5] & 0xff) + 6 + 1;
+
+ /* CDP follows */
+ uint16_t *cdp = &buf[6];
+ if (cdp[0] != 0x96 || cdp[1] != 0x69) {
+ av_log(avctx, AV_LOG_WARNING, "Invalid CDP header 0x%.2x 0x%.2x", cdp[0], cdp[1]);
+ return NULL;
+ }
+
+ len -= 7; // remove VANC header and checksum
+
+ if (cdp[2] != len) {
+ av_log(avctx, AV_LOG_WARNING, "CDP len %d != %zu", cdp[2], len);
+ return NULL;
+ }
+
+ uint8_t cdp_sum = 0;
+ for (size_t i = 0; i < len - 1; i++)
+ cdp_sum += cdp[i];
+ cdp_sum = cdp_sum ? 256 - cdp_sum : 0;
+ if (cdp[len - 1] != cdp_sum) {
+ av_log(avctx, AV_LOG_WARNING, "CDP checksum invalid 0x%.4x != 0x%.4x", cdp_sum, cdp[len-1]);
+ return NULL;
+ }
+
+ uint8_t rate = cdp[3];
+ if (!(rate & 0x0f)) {
+ av_log(avctx, AV_LOG_WARNING, "CDP frame rate invalid (0x%.2x)", rate);
+ return NULL;
+ }
+ rate >>= 4;
+ if (rate > 8) {
+ av_log(avctx, AV_LOG_WARNING, "CDP frame rate invalid (0x%.2x)", rate);
+ return NULL;
+ }
+
+ if (!(cdp[4] & 0x43)) /* ccdata_present | caption_service_active | reserved */ {
+ av_log(avctx, AV_LOG_WARNING, "CDP flags invalid (0x%.2x)", cdp[4]);
+ return NULL;
+ }
+
+ uint16_t hdr = (cdp[5] << 8) | cdp[6];
+ if (cdp[7] != 0x72) /* ccdata_id */ {
+ av_log(avctx, AV_LOG_WARNING, "Invalid ccdata_id 0x%.2x", cdp[7]);
+ return NULL;
+ }
+
+ cc_count = cdp[8];
+ if (!(cc_count & 0xe0)) {
+ av_log(avctx, AV_LOG_WARNING, "Invalid cc_count 0x%.2x", cc_count);
+ return NULL;
+ }
+
+ cc_count &= 0x1f;
+ if ((len - 13) < cc_count * 3) {
+ av_log(avctx, AV_LOG_WARNING, "Invalid cc_count %d (> %zu)", cc_count * 3, len - 13);
+ return NULL;
+ }
+
+ if (cdp[len - 4] != 0x74) /* footer id */ {
+ av_log(avctx, AV_LOG_WARNING, "Invalid footer id 0x%.2x", cdp[len-4]);
+ return NULL;
+ }
+
+ uint16_t ftr = (cdp[len - 3] << 8) | cdp[len - 2];
+ if (ftr != hdr) {
+ av_log(avctx, AV_LOG_WARNING, "Header 0x%.4x != Footer 0x%.4x", hdr, ftr);
+ return NULL;
+ }
+
+ uint8_t *cc = (uint8_t *)av_malloc(cc_count * 3);
+
+ for (size_t i = 0; i < cc_count; i++) {
+ cc[3*i + 0] = cdp[9 + 3*i+0] /* & 3 */;
+ cc[3*i + 1] = cdp[9 + 3*i+1];
+ cc[3*i + 2] = cdp[9 + 3*i+2];
+ }
+
+ cc_count *= 3;
+ return cc;
+}
+
+uint8_t *get_metadata(AVFormatContext *avctx, uint16_t *buf, size_t width,
+ uint8_t *tgt, size_t tgt_size, AVPacket *pkt)
+{
+ decklink_cctx *cctx = (struct decklink_cctx *) avctx->priv_data;
+ uint16_t did = buf[3] & 0xFF; // data id
+ uint16_t sdid = buf[4] & 0xFF; // secondary data id
+
+ static const uint8_t vanc_header[6] = { 0x00, 0x00, 0xff, 0x03, 0xff, 0x03 };
+ if (memcmp(vanc_header, buf, 3 * 2)) {
+ /* Does not start with the VANC header */
+ return tgt;
+ }
+
+ size_t len = (buf[5] & 0xff) + 6 + 1;
+ if (len > width) {
+ av_log(avctx, AV_LOG_WARNING, "Data Count (%zu) > line length (%zu)\n",
+ len, width);
+ return tgt;
+ }
+
+ uint16_t vanc_sum = 0;
+ for (size_t i = 3; i < len - 1; i++) {
+ uint16_t v = buf[i];
+ int np = v >> 8;
+ int p = parity(v & 0xff);
+ if ((!!p ^ !!(v & 0x100)) || (np != 1 && np != 2)) {
+ av_log(avctx, AV_LOG_WARNING, "Parity incorrect for word %zu\n", i);
+ return tgt;
+ }
+ vanc_sum += v;
+ vanc_sum &= 0x1ff;
+ buf[i] &= 0xff;
+ }
+
+ vanc_sum |= ((~vanc_sum & 0x100) << 1);
+ if (buf[len - 1] != vanc_sum) {
+ av_log(avctx, AV_LOG_WARNING,
+ "VANC checksum incorrect: 0x%.4x != 0x%.4x\n", vanc_sum,
+ buf[len - 1]);
+ return tgt;
+ }
+
+ if (did == 0x43 && (sdid == 0x02 || sdid == 0x03) && cctx->teletext_lines &&
+ width == 1920 && tgt_size >= 1920) {
+ tgt = teletext_data_unit_from_vanc_data(buf, tgt, cctx->teletext_lines);
+ } else if (did == 0x61 && sdid == 0x01) {
+ unsigned int data_len;
+ uint8_t *data = vanc_to_cc(avctx, buf, width, data_len);
+ if (data) {
+ uint8_t *pkt_cc = av_packet_new_side_data(pkt, AV_PKT_DATA_A53_CC, data_len);
+ memcpy(pkt_cc, data, data_len);
+ av_free(data);
+ }
+ } else {
+ av_log(avctx, AV_LOG_WARNING, "Unknown meta data DID = 0x%.2x SDID = 0x%.2x\n",
+ did, sdid);
+ }
+
+ return tgt;
+}
+
static void avpacket_queue_init(AVFormatContext *avctx, AVPacketQueue *q)
{
struct decklink_cctx *ctx = (struct decklink_cctx *)avctx->priv_data;
@@ -537,7 +690,7 @@ HRESULT decklink_input_callback::VideoInputFrameArrived(
videoFrame->GetHeight();
//fprintf(stderr,"Video Frame size %d ts %d\n", pkt.size, pkt.pts);
- if (!no_video && ctx->teletext_lines) {
+ if (!no_video) {
IDeckLinkVideoFrameAncillary *vanc;
AVPacket txt_pkt;
uint8_t txt_buf0[3531]; // 35 * 46 bytes decoded teletext lines + 1 byte data_identifier + 1920 bytes OP47 decode buffer
@@ -550,7 +703,8 @@ HRESULT decklink_input_callback::VideoInputFrameArrived(
txt_buf[0] = 0x10; // data_identifier - EBU_data
txt_buf++;
#if CONFIG_LIBZVBI
- if (ctx->bmd_mode == bmdModePAL && (vanc_format == bmdFormat8BitYUV || vanc_format == bmdFormat10BitYUV)) {
+ if (ctx->bmd_mode == bmdModePAL && ctx->teletext_lines &&
+ (vanc_format == bmdFormat8BitYUV || vanc_format == bmdFormat10BitYUV)) {
av_assert0(videoFrame->GetWidth() == 720);
for (i = 6; i < 336; i++, line_mask <<= 1) {
uint8_t *buf;
@@ -575,13 +729,8 @@ HRESULT decklink_input_callback::VideoInputFrameArrived(
if (vanc->GetBufferForVerticalBlankingLine(i, (void**)&buf) == S_OK) {
uint16_t luma_vanc[4096]; // Allocated for highest width (4K)
extract_luma_from_v210(luma_vanc, buf, videoFrame->GetWidth());
- if (videoFrame->GetWidth() == 1920) {
- txt_buf = teletext_data_unit_from_vanc_data(luma_vanc, txt_buf, ctx->teletext_lines);
- if (txt_buf - txt_buf0 > 1611) { // ensure we still have at least 1920 bytes free in the buffer
- av_log(avctx, AV_LOG_ERROR, "Too many OP47 teletext packets.\n");
- break;
- }
- }
+ txt_buf = get_metadata(avctx, luma_vanc, videoFrame->GetWidth(),
+ txt_buf, 3531 - (txt_buf - txt_buf0), &pkt);
}
if (i == field0_vanc_end)
i = field1_vanc_start;
--
1.9.1
More information about the ffmpeg-devel
mailing list