mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-17 03:35:21 +00:00
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
3 changed files with 366 additions and 4 deletions
|
@ -3535,6 +3535,13 @@ gst_video_vbi_parser_free
|
|||
gst_video_vbi_parser_add_line
|
||||
gst_video_vbi_parser_get_ancillary
|
||||
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>
|
||||
GstVideoCaptionType
|
||||
GstVideoCaptionMeta
|
||||
|
@ -3550,6 +3557,7 @@ gst_video_caption_meta_get_info
|
|||
GST_VIDEO_CAPTION_META_INFO
|
||||
gst_video_caption_meta_api_get_type
|
||||
gst_video_vbi_parser_get_type
|
||||
gst_video_vbi_encoder_get_type
|
||||
gst_video_ancillary_di_d16_get_type
|
||||
gst_video_ancillary_did_get_type
|
||||
gst_video_caption_type_get_type
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* GStreamer
|
||||
* 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
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
|
@ -388,7 +389,7 @@ gst_video_vbi_parser_free (GstVideoVBIParser * parser)
|
|||
}
|
||||
|
||||
static void
|
||||
convert_line_uyvy (GstVideoVBIParser * parser, const guint8 * data)
|
||||
convert_line_from_uyvy (GstVideoVBIParser * parser, const guint8 * data)
|
||||
{
|
||||
guint i;
|
||||
guint8 *y = parser->work_data;
|
||||
|
@ -430,7 +431,7 @@ gst_info_dump_mem16_line (gchar * linebuf, gsize linebuf_size,
|
|||
}
|
||||
|
||||
static void
|
||||
convert_line_v210 (GstVideoVBIParser * parser, const guint8 * data)
|
||||
convert_line_from_v210 (GstVideoVBIParser * parser, const guint8 * data)
|
||||
{
|
||||
guint i;
|
||||
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)) {
|
||||
case GST_VIDEO_FORMAT_v210:
|
||||
convert_line_v210 (parser, data);
|
||||
convert_line_from_v210 (parser, data);
|
||||
break;
|
||||
case GST_VIDEO_FORMAT_UYVY:
|
||||
convert_line_uyvy (parser, data);
|
||||
convert_line_from_uyvy (parser, data);
|
||||
break;
|
||||
default:
|
||||
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 *******************************************/
|
||||
|
||||
GType
|
||||
|
|
|
@ -223,6 +223,39 @@ void gst_video_vbi_parser_free (GstVideoVBIParser *parser);
|
|||
GST_VIDEO_API
|
||||
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
|
||||
|
||||
|
|
Loading…
Reference in a new issue