video-anc: Implement a VBI encoder
authorSebastian Dröge <sebastian@centricular.com>
Wed, 7 Nov 2018 13:12:13 +0000 (15:12 +0200)
committerEdward Hervey <bilboed@bilboed.com>
Mon, 12 Nov 2018 14:09:28 +0000 (14:09 +0000)
This allows writing out data from caption meta and similar to VBI

docs/libs/gst-plugins-base-libs-sections.txt
gst-libs/gst/video/video-anc.c
gst-libs/gst/video/video-anc.h

index 687c1e6..6e3204a 100644 (file)
@@ -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
index 838f531..baaaa58 100644 (file)
@@ -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
index 3c17ba0..a214a31 100644 (file)
@@ -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