Commit 9b937958 authored by Marton Balint's avatar Marton Balint

avdevice/decklink_dec: add support for receiving op47 teletext

v2:
- use uint16_t instead of int to store 10-bit ancillary data
- fix ancillary line numbers for 1080p
- some comments and clarifications as requested by Aaron Levinson
Signed-off-by: 's avatarMarton Balint <cus@passwd.hu>
parent cc0916bf
......@@ -245,13 +245,19 @@ of uyvy422. Not all Blackmagic devices support this option.
@item teletext_lines
If set to nonzero, an additional teletext stream will be captured from the
vertical ancillary data. This option is a bitmask of the VBI lines checked,
specifically lines 6 to 22, and lines 318 to 335. Line 6 is the LSB in the mask.
Selected lines which do not contain teletext information will be ignored. You
can use the special @option{all} constant to select all possible lines, or
@option{standard} to skip lines 6, 318 and 319, which are not compatible with all
receivers. Capturing teletext only works for SD PAL sources. To use this
option, ffmpeg needs to be compiled with @code{--enable-libzvbi}.
vertical ancillary data. Both SD PAL (576i) and HD (1080i or 1080p)
sources are supported. In case of HD sources, OP47 packets are decoded.
This option is a bitmask of the SD PAL VBI lines captured, specifically lines 6
to 22, and lines 318 to 335. Line 6 is the LSB in the mask. Selected lines
which do not contain teletext information will be ignored. You can use the
special @option{all} constant to select all possible lines, or
@option{standard} to skip lines 6, 318 and 319, which are not compatible with
all receivers.
For SD sources, ffmpeg needs to be compiled with @code{--enable-libzvbi}. For
HD sources, on older (pre-4K) DeckLink card models you have to capture in 10
bit mode.
@item channels
Defines number of audio channels to capture. Must be @samp{2}, @samp{8} or @samp{16}.
......
......@@ -36,6 +36,7 @@ extern "C" {
#include "libavutil/imgutils.h"
#include "libavutil/time.h"
#include "libavutil/mathematics.h"
#include "libavutil/reverse.h"
#if CONFIG_LIBZVBI
#include <libzvbi.h>
#endif
......@@ -44,7 +45,6 @@ extern "C" {
#include "decklink_common.h"
#include "decklink_dec.h"
#if CONFIG_LIBZVBI
static uint8_t calc_parity_and_line_offset(int line)
{
uint8_t ret = (line < 313) << 5;
......@@ -63,6 +63,7 @@ static void fill_data_unit_head(int line, uint8_t *tgt)
tgt[3] = 0xe4; // framing code
}
#if CONFIG_LIBZVBI
static uint8_t* teletext_data_unit_from_vbi_data(int line, uint8_t *src, uint8_t *tgt, vbi_pixfmt fmt)
{
vbi_bit_slicer slicer;
......@@ -95,6 +96,95 @@ static uint8_t* teletext_data_unit_from_vbi_data_10bit(int line, uint8_t *src, u
}
#endif
static uint8_t* teletext_data_unit_from_op47_vbi_packet(int line, uint16_t *py, uint8_t *tgt)
{
int i;
if (py[0] != 0x255 || py[1] != 0x255 || py[2] != 0x227)
return tgt;
fill_data_unit_head(line, tgt);
py += 3;
tgt += 4;
for (i = 0; i < 42; i++)
*tgt++ = ff_reverse[py[i] & 255];
return tgt;
}
static int linemask_matches(int line, int64_t mask)
{
int shift = -1;
if (line >= 6 && line <= 22)
shift = line - 6;
if (line >= 318 && line <= 335)
shift = line - 318 + 17;
return shift >= 0 && ((1ULL << shift) & mask);
}
static uint8_t* teletext_data_unit_from_op47_data(uint16_t *py, uint16_t *pend, uint8_t *tgt, int64_t wanted_lines)
{
if (py < pend - 9) {
if (py[0] == 0x151 && py[1] == 0x115 && py[3] == 0x102) { // identifier, identifier, format code for WST teletext
uint16_t *descriptors = py + 4;
int i;
py += 9;
for (i = 0; i < 5 && py < pend - 45; i++, py += 45) {
int line = (descriptors[i] & 31) + (!(descriptors[i] & 128)) * 313;
if (line && linemask_matches(line, wanted_lines))
tgt = teletext_data_unit_from_op47_vbi_packet(line, py, tgt);
}
}
}
return tgt;
}
static uint8_t* teletext_data_unit_from_ancillary_packet(uint16_t *py, uint16_t *pend, uint8_t *tgt, int64_t wanted_lines, int allow_multipacket)
{
uint16_t did = py[0]; // data id
uint16_t sdid = py[1]; // secondary data id
uint16_t dc = py[2] & 255; // data count
py += 3;
pend = FFMIN(pend, py + dc);
if (did == 0x143 && sdid == 0x102) { // subtitle distribution packet
tgt = teletext_data_unit_from_op47_data(py, pend, tgt, wanted_lines);
} else if (allow_multipacket && did == 0x143 && sdid == 0x203) { // VANC multipacket
py += 2; // priority, line/field
while (py < pend - 3) {
tgt = teletext_data_unit_from_ancillary_packet(py, pend, tgt, wanted_lines, 0);
py += 4 + (py[2] & 255); // ndid, nsdid, ndc, line/field
}
}
return tgt;
}
static uint8_t* teletext_data_unit_from_vanc_data(uint8_t *src, uint8_t *tgt, int64_t wanted_lines)
{
uint16_t y[1920];
uint16_t *py = y;
uint16_t *pend = y + 1920;
/* The 10-bit VANC data is packed in V210, we only need the luma component. */
while (py < pend) {
*py++ = (src[1] >> 2) + ((src[2] & 15) << 6);
*py++ = src[4] + ((src[5] & 3) << 8);
*py++ = (src[6] >> 4) + ((src[7] & 63) << 4);
src += 8;
}
py = y;
while (py < pend - 6) {
if (py[0] == 0 && py[1] == 0x3ff && py[2] == 0x3ff) { // ancillary data flag
py += 3;
tgt = teletext_data_unit_from_ancillary_packet(py, pend, tgt, wanted_lines, 0);
py += py[2] & 255;
} else {
py++;
}
}
return tgt;
}
static void avpacket_queue_init(AVFormatContext *avctx, AVPacketQueue *q)
{
memset(q, 0, sizeof(AVPacketQueue));
......@@ -381,11 +471,10 @@ HRESULT decklink_input_callback::VideoInputFrameArrived(
videoFrame->GetHeight();
//fprintf(stderr,"Video Frame size %d ts %d\n", pkt.size, pkt.pts);
#if CONFIG_LIBZVBI
if (!no_video && ctx->teletext_lines) {
IDeckLinkVideoFrameAncillary *vanc;
AVPacket txt_pkt;
uint8_t txt_buf0[1611]; // max 35 * 46 bytes decoded teletext lines + 1 byte data_identifier
uint8_t txt_buf0[3531]; // 35 * 46 bytes decoded teletext lines + 1 byte data_identifier + 1920 bytes OP47 decode buffer
uint8_t *txt_buf = txt_buf0;
if (videoFrame->GetAncillaryData(&vanc) == S_OK) {
......@@ -394,6 +483,7 @@ HRESULT decklink_input_callback::VideoInputFrameArrived(
BMDPixelFormat vanc_format = vanc->GetPixelFormat();
txt_buf[0] = 0x10; // data_identifier - EBU_data
txt_buf++;
#if CONFIG_LIBZVBI
if (ctx->bmd_mode == bmdModePAL && (vanc_format == bmdFormat8BitYUV || vanc_format == bmdFormat10BitYUV)) {
av_assert0(videoFrame->GetWidth() == 720);
for (i = 6; i < 336; i++, line_mask <<= 1) {
......@@ -408,6 +498,21 @@ HRESULT decklink_input_callback::VideoInputFrameArrived(
i = 317;
}
}
#endif
if (videoFrame->GetWidth() == 1920 && vanc_format == bmdFormat10BitYUV) {
int first_active_line = ctx->bmd_field_dominance == bmdProgressiveFrame ? 42 : 584;
for (i = 8; i < first_active_line; i++) {
uint8_t *buf;
if (vanc->GetBufferForVerticalBlankingLine(i, (void**)&buf) == S_OK)
txt_buf = teletext_data_unit_from_vanc_data(buf, txt_buf, ctx->teletext_lines);
if (ctx->bmd_field_dominance != bmdProgressiveFrame && i == 20) // skip field1 active lines
i = 569;
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;
}
}
}
vanc->Release();
if (txt_buf - txt_buf0 > 1) {
int stuffing_units = (4 - ((45 + txt_buf - txt_buf0) / 46) % 4) % 4;
......@@ -428,7 +533,6 @@ HRESULT decklink_input_callback::VideoInputFrameArrived(
}
}
}
#endif
if (avpacket_queue_put(&ctx->queue, &pkt) < 0) {
++ctx->dropped;
......@@ -527,13 +631,6 @@ av_cold int ff_decklink_read_header(AVFormatContext *avctx)
ctx->draw_bars = cctx->draw_bars;
cctx->ctx = ctx;
#if !CONFIG_LIBZVBI
if (ctx->teletext_lines) {
av_log(avctx, AV_LOG_ERROR, "Libzvbi support is needed for capturing teletext, please recompile FFmpeg.\n");
return AVERROR(ENOSYS);
}
#endif
/* Check audio channel option for valid values: 2, 8 or 16 */
switch (cctx->audio_channels) {
case 2:
......@@ -587,6 +684,14 @@ av_cold int ff_decklink_read_header(AVFormatContext *avctx)
}
}
#if !CONFIG_LIBZVBI
if (ctx->teletext_lines && ctx->bmd_mode == bmdModePAL) {
av_log(avctx, AV_LOG_ERROR, "Libzvbi support is needed for capturing SD PAL teletext, please recompile FFmpeg.\n");
ret = AVERROR(ENOSYS);
goto error;
}
#endif
/* Setup streams. */
st = avformat_new_stream(avctx, NULL);
if (!st) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment