video-anc: Implement a VBI encoder
This allows writing out data from caption meta and similar to VBI
This commit is contained in:
parent
369c79abd3
commit
6e9c71e6c1
@ -3535,6 +3535,13 @@ gst_video_vbi_parser_free
|
|||||||
gst_video_vbi_parser_add_line
|
gst_video_vbi_parser_add_line
|
||||||
gst_video_vbi_parser_get_ancillary
|
gst_video_vbi_parser_get_ancillary
|
||||||
gst_video_vbi_parser_copy
|
gst_video_vbi_parser_copy
|
||||||
|
|
||||||
|
GstVideoVBIEncoder
|
||||||
|
gst_video_vbi_encoder_new
|
||||||
|
gst_video_vbi_encoder_free
|
||||||
|
gst_video_vbi_encoder_add_ancillary
|
||||||
|
gst_video_vbi_encoder_write_line
|
||||||
|
gst_video_vbi_encoder_copy
|
||||||
<SUBSECTION closedcaption>
|
<SUBSECTION closedcaption>
|
||||||
GstVideoCaptionType
|
GstVideoCaptionType
|
||||||
GstVideoCaptionMeta
|
GstVideoCaptionMeta
|
||||||
@ -3550,6 +3557,7 @@ gst_video_caption_meta_get_info
|
|||||||
GST_VIDEO_CAPTION_META_INFO
|
GST_VIDEO_CAPTION_META_INFO
|
||||||
gst_video_caption_meta_api_get_type
|
gst_video_caption_meta_api_get_type
|
||||||
gst_video_vbi_parser_get_type
|
gst_video_vbi_parser_get_type
|
||||||
|
gst_video_vbi_encoder_get_type
|
||||||
gst_video_ancillary_di_d16_get_type
|
gst_video_ancillary_di_d16_get_type
|
||||||
gst_video_ancillary_did_get_type
|
gst_video_ancillary_did_get_type
|
||||||
gst_video_caption_type_get_type
|
gst_video_caption_type_get_type
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
/* GStreamer
|
/* GStreamer
|
||||||
* Copyright (C) 2018 Edward Hervey <edward@centricular.com>
|
* Copyright (C) 2018 Edward Hervey <edward@centricular.com>
|
||||||
|
* Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
|
||||||
*
|
*
|
||||||
* This library is free software; you can redistribute it and/or
|
* This library is free software; you can redistribute it and/or
|
||||||
* modify it under the terms of the GNU Library General Public
|
* modify it under the terms of the GNU Library General Public
|
||||||
@ -388,7 +389,7 @@ gst_video_vbi_parser_free (GstVideoVBIParser * parser)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
convert_line_uyvy (GstVideoVBIParser * parser, const guint8 * data)
|
convert_line_from_uyvy (GstVideoVBIParser * parser, const guint8 * data)
|
||||||
{
|
{
|
||||||
guint i;
|
guint i;
|
||||||
guint8 *y = parser->work_data;
|
guint8 *y = parser->work_data;
|
||||||
@ -430,7 +431,7 @@ gst_info_dump_mem16_line (gchar * linebuf, gsize linebuf_size,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
convert_line_v210 (GstVideoVBIParser * parser, const guint8 * data)
|
convert_line_from_v210 (GstVideoVBIParser * parser, const guint8 * data)
|
||||||
{
|
{
|
||||||
guint i;
|
guint i;
|
||||||
guint16 *y = (guint16 *) parser->work_data;
|
guint16 *y = (guint16 *) parser->work_data;
|
||||||
@ -502,10 +503,10 @@ gst_video_vbi_parser_add_line (GstVideoVBIParser * parser, const guint8 * data)
|
|||||||
|
|
||||||
switch (GST_VIDEO_INFO_FORMAT (&parser->info)) {
|
switch (GST_VIDEO_INFO_FORMAT (&parser->info)) {
|
||||||
case GST_VIDEO_FORMAT_v210:
|
case GST_VIDEO_FORMAT_v210:
|
||||||
convert_line_v210 (parser, data);
|
convert_line_from_v210 (parser, data);
|
||||||
break;
|
break;
|
||||||
case GST_VIDEO_FORMAT_UYVY:
|
case GST_VIDEO_FORMAT_UYVY:
|
||||||
convert_line_uyvy (parser, data);
|
convert_line_from_uyvy (parser, data);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
GST_ERROR ("UNSUPPORTED FORMAT !");
|
GST_ERROR ("UNSUPPORTED FORMAT !");
|
||||||
@ -514,6 +515,326 @@ gst_video_vbi_parser_add_line (GstVideoVBIParser * parser, const guint8 * data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct _GstVideoVBIEncoder
|
||||||
|
{
|
||||||
|
GstVideoInfo info; /* format of the lines provided */
|
||||||
|
guint8 *work_data; /* Converted line in planar 16bit format */
|
||||||
|
guint32 work_data_size; /* Size in bytes of work_data */
|
||||||
|
guint offset; /* Current offset (in bytes) in work_data */
|
||||||
|
gboolean bit16; /* Data is stored as 16bit if TRUE. Else 8bit(without parity) */
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_BOXED_TYPE (GstVideoVBIEncoder, gst_video_vbi_encoder,
|
||||||
|
(GBoxedCopyFunc) gst_video_vbi_encoder_copy,
|
||||||
|
(GBoxedFreeFunc) gst_video_vbi_encoder_free);
|
||||||
|
|
||||||
|
GstVideoVBIEncoder *
|
||||||
|
gst_video_vbi_encoder_copy (const GstVideoVBIEncoder * encoder)
|
||||||
|
{
|
||||||
|
GstVideoVBIEncoder *res;
|
||||||
|
|
||||||
|
g_return_val_if_fail (encoder != NULL, NULL);
|
||||||
|
|
||||||
|
res = gst_video_vbi_encoder_new (GST_VIDEO_INFO_FORMAT (&encoder->info),
|
||||||
|
encoder->info.width);
|
||||||
|
if (res) {
|
||||||
|
memcpy (res->work_data, encoder->work_data, encoder->work_data_size);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gst_video_vbi_encoder_free:
|
||||||
|
* @encoder: a #GstVideoVBIEncoder
|
||||||
|
*
|
||||||
|
* Frees the @encoder.
|
||||||
|
*
|
||||||
|
* Since: 1.16
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
gst_video_vbi_encoder_free (GstVideoVBIEncoder * encoder)
|
||||||
|
{
|
||||||
|
g_return_if_fail (encoder != NULL);
|
||||||
|
|
||||||
|
g_free (encoder->work_data);
|
||||||
|
g_free (encoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gst_video_vbi_encoder_new:
|
||||||
|
* @format: a #GstVideoFormat
|
||||||
|
* @pixel_width: The width in pixel to use
|
||||||
|
*
|
||||||
|
* Create a new #GstVideoVBIEncoder for the specified @format and @pixel_width.
|
||||||
|
*
|
||||||
|
* Since: 1.16
|
||||||
|
*
|
||||||
|
* Returns: The new #GstVideoVBIEncoder or %NULL if the @format and/or @pixel_width
|
||||||
|
* is not supported.
|
||||||
|
*/
|
||||||
|
GstVideoVBIEncoder *
|
||||||
|
gst_video_vbi_encoder_new (GstVideoFormat format, guint32 pixel_width)
|
||||||
|
{
|
||||||
|
GstVideoVBIEncoder *encoder;
|
||||||
|
|
||||||
|
g_return_val_if_fail (pixel_width > 0, NULL);
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case GST_VIDEO_FORMAT_v210:
|
||||||
|
encoder = g_new0 (GstVideoVBIEncoder, 1);
|
||||||
|
encoder->bit16 = TRUE;
|
||||||
|
break;
|
||||||
|
case GST_VIDEO_FORMAT_UYVY:
|
||||||
|
encoder = g_new0 (GstVideoVBIEncoder, 1);
|
||||||
|
encoder->bit16 = FALSE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
GST_WARNING ("Format not supported by GstVideoVBIEncoder");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_video_info_init (&encoder->info);
|
||||||
|
if (!gst_video_info_set_format (&encoder->info, format, pixel_width, 1)) {
|
||||||
|
GST_ERROR ("Could not create GstVideoInfo");
|
||||||
|
g_free (encoder);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate the workspace which is going to be 2 * pixel_width big
|
||||||
|
* 2 : number of pixels per "component" (we only deal with 4:2:2)
|
||||||
|
* We use 1 or 2 bytes per pixel depending on whether we are internally
|
||||||
|
* working in 8 or 16bit */
|
||||||
|
encoder->work_data_size = 2 * pixel_width;
|
||||||
|
if (encoder->bit16)
|
||||||
|
encoder->work_data = g_malloc0 (encoder->work_data_size * 2);
|
||||||
|
else
|
||||||
|
encoder->work_data = g_malloc0 (encoder->work_data_size);
|
||||||
|
encoder->offset = 0;
|
||||||
|
|
||||||
|
return encoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if G_GNUC_CHECK_VERSION(3,4)
|
||||||
|
static inline guint
|
||||||
|
parity (guint8 x)
|
||||||
|
{
|
||||||
|
return __builtin_parity (x);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static guint
|
||||||
|
parity (guint8 x)
|
||||||
|
{
|
||||||
|
guint count = 0;
|
||||||
|
|
||||||
|
while (x) {
|
||||||
|
count += x & 1;
|
||||||
|
x >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count & 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Odd/even parity in the upper two bits */
|
||||||
|
#define SET_WITH_PARITY(buf, val) G_STMT_START { \
|
||||||
|
*(buf) = val; \
|
||||||
|
if (parity (val)) \
|
||||||
|
*(buf) |= 0x100; \
|
||||||
|
else \
|
||||||
|
*(buf) |= 0x200; \
|
||||||
|
} G_STMT_END;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gst_video_vbi_encoder_add_ancillary:
|
||||||
|
* @encoder: a #GstVideoVBIEncoder
|
||||||
|
* @composite: %TRUE if composite ADF should be created, component otherwise
|
||||||
|
* @DID: The Data Identifier
|
||||||
|
* @SDID_block_number: The Secondary Data Identifier (if type 2) or the Data
|
||||||
|
* Block Number (if type 1)
|
||||||
|
* @data_count: The amount of data (in bytes) in @data (max 255 bytes)
|
||||||
|
* @data: (array length=data_count): The user data content of the Ancillary packet.
|
||||||
|
* Does not contain the ADF, DID, SDID nor CS.
|
||||||
|
*
|
||||||
|
* Stores Video Ancillary data, according to SMPTE-291M specification.
|
||||||
|
*
|
||||||
|
* Note that the contents of the data are always read as 8bit data (i.e. do not contain
|
||||||
|
* the parity check bits).
|
||||||
|
*
|
||||||
|
* Since: 1.16
|
||||||
|
*
|
||||||
|
* Returns: %TRUE if enough space was left in the current line, %FALSE
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
gboolean
|
||||||
|
gst_video_vbi_encoder_add_ancillary (GstVideoVBIEncoder * encoder,
|
||||||
|
gboolean composite, guint8 DID, guint8 SDID_block_number,
|
||||||
|
const guint8 * data, guint data_count)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (encoder != NULL, FALSE);
|
||||||
|
g_return_val_if_fail (data != NULL, FALSE);
|
||||||
|
g_return_val_if_fail (data_count < 256, FALSE);
|
||||||
|
|
||||||
|
/* Doesn't fit into this line anymore */
|
||||||
|
if (encoder->offset + data_count + (composite ? 5 : 7) >
|
||||||
|
encoder->work_data_size)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (encoder->bit16) {
|
||||||
|
guint16 *work_data = ((guint16 *) encoder->work_data) + encoder->offset;
|
||||||
|
guint i = 0, j;
|
||||||
|
guint checksum = 0;
|
||||||
|
|
||||||
|
/* Write ADF */
|
||||||
|
if (composite) {
|
||||||
|
work_data[i] = 0x3fc;
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
work_data[i] = 0x000;
|
||||||
|
work_data[i + 1] = 0x3ff;
|
||||||
|
work_data[i + 2] = 0x3ff;
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
SET_WITH_PARITY (&work_data[i], DID);
|
||||||
|
SET_WITH_PARITY (&work_data[i + 1], SDID_block_number);
|
||||||
|
SET_WITH_PARITY (&work_data[i + 2], data_count);
|
||||||
|
i += 3;
|
||||||
|
|
||||||
|
for (j = 0; j < data_count; j++)
|
||||||
|
SET_WITH_PARITY (&work_data[i + j], data[j]);
|
||||||
|
i += data_count;
|
||||||
|
|
||||||
|
for (j = (composite ? 1 : 3); j < i; j++)
|
||||||
|
checksum += work_data[j];
|
||||||
|
checksum &= 0x1ff;
|
||||||
|
checksum |= (!(checksum >> 8)) << 9;
|
||||||
|
|
||||||
|
work_data[i] = checksum;
|
||||||
|
i += 1;
|
||||||
|
|
||||||
|
encoder->offset += i;
|
||||||
|
} else {
|
||||||
|
guint8 *work_data = ((guint8 *) encoder->work_data) + encoder->offset;
|
||||||
|
guint i = 0, j;
|
||||||
|
guint checksum = 0;
|
||||||
|
|
||||||
|
/* Write ADF */
|
||||||
|
if (composite) {
|
||||||
|
work_data[i] = 0xfc;
|
||||||
|
i += 1;
|
||||||
|
} else {
|
||||||
|
work_data[i] = 0x00;
|
||||||
|
work_data[i + 1] = 0xff;
|
||||||
|
work_data[i + 2] = 0xff;
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
work_data[i] = DID;
|
||||||
|
work_data[i + 1] = SDID_block_number;
|
||||||
|
work_data[i + 2] = data_count;
|
||||||
|
i += 3;
|
||||||
|
|
||||||
|
for (j = 0; j < data_count; j++)
|
||||||
|
work_data[i + j] = data[j];
|
||||||
|
i += data_count;
|
||||||
|
|
||||||
|
for (j = (composite ? 1 : 3); j < i; j++)
|
||||||
|
checksum += work_data[j];
|
||||||
|
checksum &= 0xff;
|
||||||
|
|
||||||
|
work_data[i] = checksum;
|
||||||
|
i += 1;
|
||||||
|
|
||||||
|
encoder->offset += i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_line_to_v210 (GstVideoVBIEncoder * encoder, guint8 * data)
|
||||||
|
{
|
||||||
|
guint i;
|
||||||
|
const guint16 *y = (const guint16 *) encoder->work_data;
|
||||||
|
const guint16 *uv = y + encoder->info.width;
|
||||||
|
guint32 a, b, c, d;
|
||||||
|
|
||||||
|
/* Convert the line */
|
||||||
|
for (i = 0; i < encoder->info.width - 5; i += 6) {
|
||||||
|
a = ((uv[0] & 0x3ff) << 0)
|
||||||
|
| ((y[0] & 0x3ff) << 10)
|
||||||
|
| ((uv[1] & 0x3ff) << 20);
|
||||||
|
uv += 2;
|
||||||
|
y++;
|
||||||
|
|
||||||
|
b = ((y[0] & 0x3ff) << 0)
|
||||||
|
| ((uv[0] & 0x3ff) << 10)
|
||||||
|
| ((y[1] & 0x3ff) << 20);
|
||||||
|
y += 2;
|
||||||
|
uv++;
|
||||||
|
|
||||||
|
c = ((uv[0] & 0x3ff) << 0)
|
||||||
|
| ((y[0] & 0x3ff) << 10)
|
||||||
|
| ((uv[1] & 0x3ff) << 20);
|
||||||
|
uv += 2;
|
||||||
|
y++;
|
||||||
|
|
||||||
|
d = ((y[0] & 0x3ff) << 0)
|
||||||
|
| ((uv[0] & 0x3ff) << 10)
|
||||||
|
| ((y[1] & 0x3ff) << 20);
|
||||||
|
y += 2;
|
||||||
|
uv++;
|
||||||
|
|
||||||
|
GST_WRITE_UINT32_LE (data + (i / 6) * 16 + 0, a);
|
||||||
|
GST_WRITE_UINT32_LE (data + (i / 6) * 16 + 4, b);
|
||||||
|
GST_WRITE_UINT32_LE (data + (i / 6) * 16 + 8, c);
|
||||||
|
GST_WRITE_UINT32_LE (data + (i / 6) * 16 + 12, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
convert_line_to_uyvy (GstVideoVBIEncoder * encoder, guint8 * data)
|
||||||
|
{
|
||||||
|
guint i;
|
||||||
|
const guint8 *y = encoder->work_data;
|
||||||
|
const guint8 *uv = y + encoder->info.width;
|
||||||
|
|
||||||
|
for (i = 0; i < encoder->info.width - 3; i += 4) {
|
||||||
|
data[(i / 4) * 4 + 0] = *uv++;
|
||||||
|
data[(i / 4) * 4 + 1] = *y++;
|
||||||
|
data[(i / 4) * 4 + 2] = *uv++;
|
||||||
|
data[(i / 4) * 4 + 3] = *y++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
gst_video_vbi_encoder_write_line (GstVideoVBIEncoder * encoder, guint8 * data)
|
||||||
|
{
|
||||||
|
g_return_if_fail (encoder != NULL);
|
||||||
|
g_return_if_fail (data != NULL);
|
||||||
|
|
||||||
|
/* nothing to write? just exit early */
|
||||||
|
if (!encoder->offset)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (GST_VIDEO_INFO_FORMAT (&encoder->info)) {
|
||||||
|
case GST_VIDEO_FORMAT_v210:
|
||||||
|
convert_line_to_v210 (encoder, data);
|
||||||
|
break;
|
||||||
|
case GST_VIDEO_FORMAT_UYVY:
|
||||||
|
convert_line_to_uyvy (encoder, data);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
GST_ERROR ("UNSUPPORTED FORMAT !");
|
||||||
|
g_assert_not_reached ();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder->offset = 0;
|
||||||
|
memset (encoder->work_data, 0,
|
||||||
|
encoder->work_data_size * (encoder->bit16 ? 2 : 1));
|
||||||
|
}
|
||||||
|
|
||||||
/* Closed Caption Meta implementation *******************************************/
|
/* Closed Caption Meta implementation *******************************************/
|
||||||
|
|
||||||
GType
|
GType
|
||||||
|
@ -223,6 +223,39 @@ void gst_video_vbi_parser_free (GstVideoVBIParser *parser);
|
|||||||
GST_VIDEO_API
|
GST_VIDEO_API
|
||||||
void gst_video_vbi_parser_add_line (GstVideoVBIParser *parser, const guint8 *data);
|
void gst_video_vbi_parser_add_line (GstVideoVBIParser *parser, const guint8 *data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstVideoVBIEncoder:
|
||||||
|
*
|
||||||
|
* An encoder for writing ancillary data to the
|
||||||
|
* Vertical Blanking Interval lines of component signals.
|
||||||
|
*
|
||||||
|
* Since: 1.16
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct _GstVideoVBIEncoder GstVideoVBIEncoder;
|
||||||
|
|
||||||
|
GST_VIDEO_API
|
||||||
|
GType gst_video_vbi_encoder_get_type (void);
|
||||||
|
|
||||||
|
GST_VIDEO_API
|
||||||
|
GstVideoVBIEncoder *gst_video_vbi_encoder_new (GstVideoFormat format, guint32 pixel_width);
|
||||||
|
|
||||||
|
GST_VIDEO_API
|
||||||
|
GstVideoVBIEncoder *gst_video_vbi_encoder_copy (const GstVideoVBIEncoder *encoder);
|
||||||
|
|
||||||
|
GST_VIDEO_API
|
||||||
|
void gst_video_vbi_encoder_free (GstVideoVBIEncoder *encoder);
|
||||||
|
|
||||||
|
GST_VIDEO_API
|
||||||
|
gboolean gst_video_vbi_encoder_add_ancillary (GstVideoVBIEncoder *encoder,
|
||||||
|
gboolean composite,
|
||||||
|
guint8 DID,
|
||||||
|
guint8 SDID_block_number,
|
||||||
|
const guint8 *data,
|
||||||
|
guint data_count);
|
||||||
|
|
||||||
|
GST_VIDEO_API
|
||||||
|
void gst_video_vbi_encoder_write_line (GstVideoVBIEncoder *encoder, guint8 *data);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user