From 6e9c71e6c155992f5e7903cf46d6355e4e1df277 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Wed, 7 Nov 2018 15:12:13 +0200 Subject: [PATCH] video-anc: Implement a VBI encoder This allows writing out data from caption meta and similar to VBI --- docs/libs/gst-plugins-base-libs-sections.txt | 8 + gst-libs/gst/video/video-anc.c | 329 ++++++++++++++++++- gst-libs/gst/video/video-anc.h | 33 ++ 3 files changed, 366 insertions(+), 4 deletions(-) diff --git a/docs/libs/gst-plugins-base-libs-sections.txt b/docs/libs/gst-plugins-base-libs-sections.txt index 687c1e67d3..6e3204a277 100644 --- a/docs/libs/gst-plugins-base-libs-sections.txt +++ b/docs/libs/gst-plugins-base-libs-sections.txt @@ -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 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 diff --git a/gst-libs/gst/video/video-anc.c b/gst-libs/gst/video/video-anc.c index 838f5315fc..baaaa5877c 100644 --- a/gst-libs/gst/video/video-anc.c +++ b/gst-libs/gst/video/video-anc.c @@ -1,5 +1,6 @@ /* GStreamer * Copyright (C) 2018 Edward Hervey + * Copyright (C) 2018 Sebastian Dröge * * 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 diff --git a/gst-libs/gst/video/video-anc.h b/gst-libs/gst/video/video-anc.h index 3c17ba0a4f..a214a31cc1 100644 --- a/gst-libs/gst/video/video-anc.h +++ b/gst-libs/gst/video/video-anc.h @@ -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