Skip to content

Commit b6cf66a

Browse files
jkarthic-akamaicus
authored andcommitted
avdevice/decklink_dec: Added Closed caption decode from VANC
Signed-off-by: Karthick J <kjeyapal@akamai.com> Signed-off-by: Marton Balint <cus@passwd.hu>
1 parent a875578 commit b6cf66a

File tree

5 files changed

+191
-18
lines changed

5 files changed

+191
-18
lines changed

doc/APIchanges

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ libavutil: 2015-08-28
1515

1616
API changes, most recent first:
1717

18+
2017-09-28 - xxxxxxx - lavc 57.106.104 - avcodec.h
19+
Add AV_PKT_DATA_A53_CC packet side data, to export closed captions
20+
1821
2017-09-27 - xxxxxxx - lavu 55.77.101 / lavu 55.31.1 - frame.h
1922
Allow passing the value of 0 (meaning "automatic") as the required alignment
2023
to av_frame_get_buffer().

libavcodec/avcodec.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1599,6 +1599,13 @@ enum AVPacketSideDataType {
15991599
*/
16001600
AV_PKT_DATA_CONTENT_LIGHT_LEVEL,
16011601

1602+
/**
1603+
* ATSC A53 Part 4 Closed Captions. This metadata should be associated with
1604+
* a video stream. A53 CC bitstream is stored as uint8_t in AVPacketSideData.data.
1605+
* The number of bytes of CC data is AVPacketSideData.size.
1606+
*/
1607+
AV_PKT_DATA_A53_CC,
1608+
16021609
/**
16031610
* The number of side data elements (in fact a bit more than it).
16041611
* This is not part of the public API/ABI in the sense that it may

libavcodec/avpacket.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ const char *av_packet_side_data_name(enum AVPacketSideDataType type)
387387
case AV_PKT_DATA_MASTERING_DISPLAY_METADATA: return "Mastering display metadata";
388388
case AV_PKT_DATA_CONTENT_LIGHT_LEVEL: return "Content light level metadata";
389389
case AV_PKT_DATA_SPHERICAL: return "Spherical Mapping";
390+
case AV_PKT_DATA_A53_CC: return "A53 Closed Captions";
390391
}
391392
return NULL;
392393
}

libavcodec/version.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
#define LIBAVCODEC_VERSION_MAJOR 57
3131
#define LIBAVCODEC_VERSION_MINOR 106
32-
#define LIBAVCODEC_VERSION_MICRO 103
32+
#define LIBAVCODEC_VERSION_MICRO 104
3333

3434
#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
3535
LIBAVCODEC_VERSION_MINOR, \

libavdevice/decklink_dec.cpp

Lines changed: 179 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/*
22
* Blackmagic DeckLink input
33
* Copyright (c) 2013-2014 Luca Barbato, Deti Fliegl
4+
* Copyright (c) 2014 Rafaël Carré
45
* Copyright (c) 2017 Akamai Technologies, Inc.
56
*
67
* This file is part of FFmpeg.
@@ -105,6 +106,42 @@ static int get_vanc_line_idx(BMDDisplayMode mode)
105106
return i - 1;
106107
}
107108

109+
static inline uint16_t parity (uint16_t x)
110+
{
111+
uint16_t i;
112+
for (i = 4 * sizeof (x); i > 0; i /= 2)
113+
x ^= x >> i;
114+
return x & 1;
115+
}
116+
117+
static inline void clear_parity_bits(uint16_t *buf, int len) {
118+
int i;
119+
for (i = 0; i < len; i++)
120+
buf[i] &= 0xff;
121+
}
122+
123+
static int check_vanc_parity_checksum(uint16_t *buf, int len, uint16_t checksum) {
124+
int i;
125+
uint16_t vanc_sum = 0;
126+
for (i = 3; i < len - 1; i++) {
127+
uint16_t v = buf[i];
128+
int np = v >> 8;
129+
int p = parity(v & 0xff);
130+
if ((!!p ^ !!(v & 0x100)) || (np != 1 && np != 2)) {
131+
// Parity check failed
132+
return -1;
133+
}
134+
vanc_sum += v;
135+
}
136+
vanc_sum &= 0x1ff;
137+
vanc_sum |= ((~vanc_sum & 0x100) << 1);
138+
if (checksum != vanc_sum) {
139+
// Checksum verification failed
140+
return -1;
141+
}
142+
return 0;
143+
}
144+
108145
/* The 10-bit VANC data is packed in V210, we only need the luma component. */
109146
static void extract_luma_from_v210(uint16_t *dst, const uint8_t *src, int width)
110147
{
@@ -232,19 +269,148 @@ static uint8_t* teletext_data_unit_from_ancillary_packet(uint16_t *py, uint16_t
232269
return tgt;
233270
}
234271

235-
static uint8_t* teletext_data_unit_from_vanc_data(uint16_t *py, uint8_t *tgt, int64_t wanted_lines)
272+
uint8_t *vanc_to_cc(AVFormatContext *avctx, uint16_t *buf, size_t words,
273+
unsigned &cc_count)
236274
{
237-
uint16_t *pend = py + 1920;
275+
size_t i, len = (buf[5] & 0xff) + 6 + 1;
276+
uint8_t cdp_sum, rate;
277+
uint16_t hdr, ftr;
278+
uint8_t *cc;
279+
uint16_t *cdp = &buf[6]; // CDP follows
280+
if (cdp[0] != 0x96 || cdp[1] != 0x69) {
281+
av_log(avctx, AV_LOG_WARNING, "Invalid CDP header 0x%.2x 0x%.2x\n", cdp[0], cdp[1]);
282+
return NULL;
283+
}
238284

239-
while (py < pend - 6) {
240-
if (py[0] == 0 && py[1] == 0x3ff && py[2] == 0x3ff) { // ancillary data flag
241-
py += 3;
242-
tgt = teletext_data_unit_from_ancillary_packet(py, pend, tgt, wanted_lines, 0);
243-
py += py[2] & 255;
285+
len -= 7; // remove VANC header and checksum
286+
287+
if (cdp[2] != len) {
288+
av_log(avctx, AV_LOG_WARNING, "CDP len %d != %zu\n", cdp[2], len);
289+
return NULL;
290+
}
291+
292+
cdp_sum = 0;
293+
for (i = 0; i < len - 1; i++)
294+
cdp_sum += cdp[i];
295+
cdp_sum = cdp_sum ? 256 - cdp_sum : 0;
296+
if (cdp[len - 1] != cdp_sum) {
297+
av_log(avctx, AV_LOG_WARNING, "CDP checksum invalid 0x%.4x != 0x%.4x\n", cdp_sum, cdp[len-1]);
298+
return NULL;
299+
}
300+
301+
rate = cdp[3];
302+
if (!(rate & 0x0f)) {
303+
av_log(avctx, AV_LOG_WARNING, "CDP frame rate invalid (0x%.2x)\n", rate);
304+
return NULL;
305+
}
306+
rate >>= 4;
307+
if (rate > 8) {
308+
av_log(avctx, AV_LOG_WARNING, "CDP frame rate invalid (0x%.2x)\n", rate);
309+
return NULL;
310+
}
311+
312+
if (!(cdp[4] & 0x43)) /* ccdata_present | caption_service_active | reserved */ {
313+
av_log(avctx, AV_LOG_WARNING, "CDP flags invalid (0x%.2x)\n", cdp[4]);
314+
return NULL;
315+
}
316+
317+
hdr = (cdp[5] << 8) | cdp[6];
318+
if (cdp[7] != 0x72) /* ccdata_id */ {
319+
av_log(avctx, AV_LOG_WARNING, "Invalid ccdata_id 0x%.2x\n", cdp[7]);
320+
return NULL;
321+
}
322+
323+
cc_count = cdp[8];
324+
if (!(cc_count & 0xe0)) {
325+
av_log(avctx, AV_LOG_WARNING, "Invalid cc_count 0x%.2x\n", cc_count);
326+
return NULL;
327+
}
328+
329+
cc_count &= 0x1f;
330+
if ((len - 13) < cc_count * 3) {
331+
av_log(avctx, AV_LOG_WARNING, "Invalid cc_count %d (> %zu)\n", cc_count * 3, len - 13);
332+
return NULL;
333+
}
334+
335+
if (cdp[len - 4] != 0x74) /* footer id */ {
336+
av_log(avctx, AV_LOG_WARNING, "Invalid footer id 0x%.2x\n", cdp[len-4]);
337+
return NULL;
338+
}
339+
340+
ftr = (cdp[len - 3] << 8) | cdp[len - 2];
341+
if (ftr != hdr) {
342+
av_log(avctx, AV_LOG_WARNING, "Header 0x%.4x != Footer 0x%.4x\n", hdr, ftr);
343+
return NULL;
344+
}
345+
346+
cc = (uint8_t *)av_malloc(cc_count * 3);
347+
if (cc == NULL) {
348+
av_log(avctx, AV_LOG_WARNING, "CC - av_malloc failed for cc_count = %d\n", cc_count);
349+
return NULL;
350+
}
351+
352+
for (size_t i = 0; i < cc_count; i++) {
353+
cc[3*i + 0] = cdp[9 + 3*i+0] /* & 3 */;
354+
cc[3*i + 1] = cdp[9 + 3*i+1];
355+
cc[3*i + 2] = cdp[9 + 3*i+2];
356+
}
357+
358+
cc_count *= 3;
359+
return cc;
360+
}
361+
362+
uint8_t *get_metadata(AVFormatContext *avctx, uint16_t *buf, size_t width,
363+
uint8_t *tgt, size_t tgt_size, AVPacket *pkt)
364+
{
365+
decklink_cctx *cctx = (struct decklink_cctx *) avctx->priv_data;
366+
uint16_t *max_buf = buf + width;
367+
368+
while (buf < max_buf - 6) {
369+
int len;
370+
uint16_t did = buf[3] & 0xFF; // data id
371+
uint16_t sdid = buf[4] & 0xFF; // secondary data id
372+
/* Check for VANC header */
373+
if (buf[0] != 0 || buf[1] != 0x3ff || buf[2] != 0x3ff) {
374+
return tgt;
375+
}
376+
377+
len = (buf[5] & 0xff) + 6 + 1;
378+
if (len > max_buf - buf) {
379+
av_log(avctx, AV_LOG_WARNING, "Data Count (%d) > data left (%zu)\n",
380+
len, max_buf - buf);
381+
return tgt;
382+
}
383+
384+
if (did == 0x43 && (sdid == 0x02 || sdid == 0x03) && cctx->teletext_lines &&
385+
width == 1920 && tgt_size >= 1920) {
386+
if (check_vanc_parity_checksum(buf, len, buf[len - 1]) < 0) {
387+
av_log(avctx, AV_LOG_WARNING, "VANC parity or checksum incorrect\n");
388+
goto skip_packet;
389+
}
390+
tgt = teletext_data_unit_from_ancillary_packet(buf + 3, buf + len, tgt, cctx->teletext_lines, 0);
391+
} else if (did == 0x61 && sdid == 0x01) {
392+
unsigned int data_len;
393+
uint8_t *data;
394+
if (check_vanc_parity_checksum(buf, len, buf[len - 1]) < 0) {
395+
av_log(avctx, AV_LOG_WARNING, "VANC parity or checksum incorrect\n");
396+
goto skip_packet;
397+
}
398+
clear_parity_bits(buf, len);
399+
data = vanc_to_cc(avctx, buf, width, data_len);
400+
if (data) {
401+
uint8_t *pkt_cc = av_packet_new_side_data(pkt, AV_PKT_DATA_A53_CC, data_len);
402+
if (pkt_cc)
403+
memcpy(pkt_cc, data, data_len);
404+
av_free(data);
405+
}
244406
} else {
245-
py++;
407+
av_log(avctx, AV_LOG_DEBUG, "Unknown meta data DID = 0x%.2x SDID = 0x%.2x\n",
408+
did, sdid);
246409
}
410+
skip_packet:
411+
buf += len;
247412
}
413+
248414
return tgt;
249415
}
250416

@@ -536,7 +702,7 @@ HRESULT decklink_input_callback::VideoInputFrameArrived(
536702
videoFrame->GetHeight();
537703
//fprintf(stderr,"Video Frame size %d ts %d\n", pkt.size, pkt.pts);
538704

539-
if (!no_video && ctx->teletext_lines) {
705+
if (!no_video) {
540706
IDeckLinkVideoFrameAncillary *vanc;
541707
AVPacket txt_pkt;
542708
uint8_t txt_buf0[3531]; // 35 * 46 bytes decoded teletext lines + 1 byte data_identifier + 1920 bytes OP47 decode buffer
@@ -549,7 +715,8 @@ HRESULT decklink_input_callback::VideoInputFrameArrived(
549715
txt_buf[0] = 0x10; // data_identifier - EBU_data
550716
txt_buf++;
551717
#if CONFIG_LIBZVBI
552-
if (ctx->bmd_mode == bmdModePAL && (vanc_format == bmdFormat8BitYUV || vanc_format == bmdFormat10BitYUV)) {
718+
if (ctx->bmd_mode == bmdModePAL && ctx->teletext_lines &&
719+
(vanc_format == bmdFormat8BitYUV || vanc_format == bmdFormat10BitYUV)) {
553720
av_assert0(videoFrame->GetWidth() == 720);
554721
for (i = 6; i < 336; i++, line_mask <<= 1) {
555722
uint8_t *buf;
@@ -571,13 +738,8 @@ HRESULT decklink_input_callback::VideoInputFrameArrived(
571738
if (vanc->GetBufferForVerticalBlankingLine(i, (void**)&buf) == S_OK) {
572739
uint16_t luma_vanc[MAX_WIDTH_VANC];
573740
extract_luma_from_v210(luma_vanc, buf, videoFrame->GetWidth());
574-
if (videoFrame->GetWidth() == 1920) {
575-
txt_buf = teletext_data_unit_from_vanc_data(luma_vanc, txt_buf, ctx->teletext_lines);
576-
if (txt_buf - txt_buf0 > 1611) { // ensure we still have at least 1920 bytes free in the buffer
577-
av_log(avctx, AV_LOG_ERROR, "Too many OP47 teletext packets.\n");
578-
break;
579-
}
580-
}
741+
txt_buf = get_metadata(avctx, luma_vanc, videoFrame->GetWidth(),
742+
txt_buf, sizeof(txt_buf0) - (txt_buf - txt_buf0), &pkt);
581743
}
582744
if (i == vanc_line_numbers[idx].field0_vanc_end)
583745
i = vanc_line_numbers[idx].field1_vanc_start - 1;

0 commit comments

Comments
 (0)