h264parser: Add a helper method to create and inject raw SEI data
authorSeungha Yang <seungha@centricular.com>
Wed, 25 Mar 2020 08:20:13 +0000 (17:20 +0900)
committerGStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Mon, 30 Mar 2020 07:59:10 +0000 (07:59 +0000)
Add an API to create raw SEI nal unit. This would be useful in case
an user want to create SEI nal data and inject the SEI nal data
into bitstream.

gst-libs/gst/codecparsers/gsth264parser.c
gst-libs/gst/codecparsers/gsth264parser.h
tests/check/libs/h264parser.c

index 83b675b..9e664fa 100644 (file)
@@ -2639,3 +2639,483 @@ gst_h264_video_calculate_framerate (const GstH264SPS * sps,
   *fps_num = num;
   *fps_den = den;
 }
+
+static gboolean
+gst_h264_write_sei_registered_user_data (NalWriter * nw,
+    GstH264RegisteredUserData * rud)
+{
+  WRITE_UINT8 (nw, rud->country_code, 8);
+  if (rud->country_code == 0xff)
+    WRITE_UINT8 (nw, rud->country_code_extension, 8);
+
+  WRITE_BYTES (nw, rud->data, rud->size);
+
+  return TRUE;
+
+error:
+  return FALSE;
+}
+
+static gboolean
+gst_h264_write_sei_frame_packing (NalWriter * nw,
+    GstH264FramePacking * frame_packing)
+{
+  WRITE_UE (nw, frame_packing->frame_packing_id);
+  WRITE_UINT8 (nw, frame_packing->frame_packing_cancel_flag, 1);
+
+  if (!frame_packing->frame_packing_cancel_flag) {
+    WRITE_UINT8 (nw, frame_packing->frame_packing_type, 7);
+    WRITE_UINT8 (nw, frame_packing->quincunx_sampling_flag, 1);
+    WRITE_UINT8 (nw, frame_packing->content_interpretation_type, 6);
+    WRITE_UINT8 (nw, frame_packing->spatial_flipping_flag, 1);
+    WRITE_UINT8 (nw, frame_packing->frame0_flipped_flag, 1);
+    WRITE_UINT8 (nw, frame_packing->field_views_flag, 1);
+    WRITE_UINT8 (nw, frame_packing->current_frame_is_frame0_flag, 1);
+    WRITE_UINT8 (nw, frame_packing->frame0_self_contained_flag, 1);
+    WRITE_UINT8 (nw, frame_packing->frame1_self_contained_flag, 1);
+
+    if (!frame_packing->quincunx_sampling_flag &&
+        frame_packing->frame_packing_type !=
+        GST_H264_FRAME_PACKING_TEMPORAL_INTERLEAVING) {
+      WRITE_UINT8 (nw, frame_packing->frame0_grid_position_x, 4);
+      WRITE_UINT8 (nw, frame_packing->frame0_grid_position_y, 4);
+      WRITE_UINT8 (nw, frame_packing->frame1_grid_position_x, 4);
+      WRITE_UINT8 (nw, frame_packing->frame1_grid_position_y, 4);
+    }
+
+    /* frame_packing_arrangement_reserved_byte */
+    WRITE_UINT8 (nw, 0, 8);
+    WRITE_UE (nw, frame_packing->frame_packing_repetition_period);
+  }
+
+  /* frame_packing_arrangement_extension_flag */
+  WRITE_UINT8 (nw, 0, 1);
+
+  return TRUE;
+
+error:
+  return FALSE;
+}
+
+static gboolean
+gst_h264_write_sei_mastering_display_colour_volume (NalWriter * nw,
+    GstH264MasteringDisplayColourVolume * mdcv)
+{
+  gint i;
+
+  for (i = 0; i < 3; i++) {
+    WRITE_UINT16 (nw, mdcv->display_primaries_x[i], 16);
+    WRITE_UINT16 (nw, mdcv->display_primaries_y[i], 16);
+  }
+
+  WRITE_UINT16 (nw, mdcv->white_point_x, 16);
+  WRITE_UINT16 (nw, mdcv->white_point_y, 16);
+  WRITE_UINT32 (nw, mdcv->max_display_mastering_luminance, 32);
+  WRITE_UINT32 (nw, mdcv->min_display_mastering_luminance, 32);
+
+  return TRUE;
+
+error:
+  return FALSE;
+}
+
+static gboolean
+gst_h264_write_sei_content_light_level_info (NalWriter * nw,
+    GstH264ContentLightLevel * cll)
+{
+  WRITE_UINT16 (nw, cll->max_content_light_level, 16);
+  WRITE_UINT16 (nw, cll->max_pic_average_light_level, 16);
+
+  return TRUE;
+
+error:
+  return FALSE;
+}
+
+static GstMemory *
+gst_h264_create_sei_memory_internal (guint8 nal_prefix_size,
+    gboolean packetized, GArray * messages)
+{
+  NalWriter nw;
+  gint i;
+  gboolean have_written_data = FALSE;
+
+  nal_writer_init (&nw, nal_prefix_size, packetized);
+
+  if (messages->len == 0)
+    goto error;
+
+  GST_DEBUG ("Create SEI nal from array, len: %d", messages->len);
+
+  /* nal header */
+  /* forbidden_zero_bit */
+  WRITE_UINT8 (&nw, 0, 1);
+  /* nal_ref_idc, zero for sei nalu */
+  WRITE_UINT8 (&nw, 0, 2);
+  /* nal_unit_type */
+  WRITE_UINT8 (&nw, GST_H264_NAL_SEI, 5);
+
+  for (i = 0; i < messages->len; i++) {
+    GstH264SEIMessage *msg = &g_array_index (messages, GstH264SEIMessage, i);
+    guint32 payload_size_data = 0;
+    guint32 payload_size_in_bits = 0;
+    guint32 payload_type_data = msg->payloadType;
+    gboolean need_align = FALSE;
+
+    switch (payload_type_data) {
+      case GST_H264_SEI_REGISTERED_USER_DATA:{
+        GstH264RegisteredUserData *rud = &msg->payload.registered_user_data;
+
+        /* itu_t_t35_country_code: 8 bits */
+        payload_size_data = 1;
+        if (rud->country_code == 0xff) {
+          /* itu_t_t35_country_code_extension_byte */
+          payload_size_data++;
+        }
+
+        payload_size_data += rud->size;
+        break;
+      }
+      case GST_H264_SEI_FRAME_PACKING:{
+        GstH264FramePacking *frame_packing = &msg->payload.frame_packing;
+        guint leading_zeros, rest;
+
+        /* frame_packing_arrangement_id: exp-golomb bits */
+        count_exp_golomb_bits (frame_packing->frame_packing_id,
+            &leading_zeros, &rest);
+        payload_size_in_bits = leading_zeros + rest;
+
+        /* frame_packing_arrangement_cancel_flag: 1 bit */
+        payload_size_in_bits++;
+        if (!frame_packing->frame_packing_cancel_flag) {
+          /* frame_packing_arrangement_type: 7 bits
+           * quincunx_sampling_flag: 1 bit
+           * content_interpretation_type: 6 bit
+           * spatial_flipping_flag: 1 bit
+           * frame0_flipped_flag: 1 bit
+           * field_views_flag: 1 bit
+           * current_frame_is_frame0_flag: 1 bit
+           * frame0_self_contained_flag: 1 bit
+           * frame1_self_contained_flag: 1 bit
+           */
+          payload_size_in_bits += 20;
+
+          if (!frame_packing->quincunx_sampling_flag &&
+              frame_packing->frame_packing_type !=
+              GST_H264_FRAME_PACKING_TEMPORAL_INTERLEAVING) {
+            /* frame0_grid_position_x: 4bits
+             * frame0_grid_position_y: 4bits
+             * frame1_grid_position_x: 4bits
+             * frame1_grid_position_y: 4bits
+             */
+            payload_size_in_bits += 16;
+          }
+
+          /* frame_packing_arrangement_reserved_byte: 8 bits */
+          payload_size_in_bits += 8;
+
+          /* frame_packing_arrangement_repetition_period: exp-golomb bits */
+          count_exp_golomb_bits (frame_packing->frame_packing_repetition_period,
+              &leading_zeros, &rest);
+          payload_size_in_bits += (leading_zeros + rest);
+        }
+        /* frame_packing_arrangement_extension_flag: 1 bit */
+        payload_size_in_bits++;
+
+        payload_size_data = payload_size_in_bits >> 3;
+
+        if ((payload_size_in_bits & 0x7) != 0) {
+          GST_INFO ("Bits for Frame Packing SEI is not byte aligned");
+          payload_size_data++;
+          need_align = TRUE;
+        }
+        break;
+      }
+      case GST_H264_SEI_MASTERING_DISPLAY_COLOUR_VOLUME:
+        /* x, y 16 bits per RGB channel
+         * x, y 16 bits white point
+         * max, min luminance 32 bits
+         *
+         * (2 * 2 * 3) + (2 * 2) + (4 * 2) = 24 bytes
+         */
+        payload_size_data = 24;
+        break;
+      case GST_H264_SEI_CONTENT_LIGHT_LEVEL:
+        /* maxCLL and maxFALL per 16 bits
+         *
+         * 2 * 2 = 4 bytes
+         */
+        payload_size_data = 4;
+        break;
+      default:
+        break;
+    }
+
+    if (payload_size_data == 0) {
+      GST_FIXME ("Unsupported SEI type %d", msg->payloadType);
+      continue;
+    }
+
+    /* write payload type bytes */
+    while (payload_type_data >= 0xff) {
+      WRITE_UINT8 (&nw, 0xff, 8);
+      payload_type_data -= -0xff;
+    }
+    WRITE_UINT8 (&nw, payload_type_data, 8);
+
+    /* write payload size bytes */
+    while (payload_size_data >= 0xff) {
+      WRITE_UINT8 (&nw, 0xff, 8);
+      payload_size_data -= -0xff;
+    }
+    WRITE_UINT8 (&nw, payload_size_data, 8);
+
+    switch (msg->payloadType) {
+      case GST_H264_SEI_REGISTERED_USER_DATA:
+        GST_DEBUG ("Writing \"Registered user data\" done");
+        if (!gst_h264_write_sei_registered_user_data (&nw,
+                &msg->payload.registered_user_data)) {
+          GST_WARNING ("Failed to write \"Registered user data\"");
+          goto error;
+        }
+        have_written_data = TRUE;
+        break;
+      case GST_H264_SEI_FRAME_PACKING:
+        GST_DEBUG ("Writing \"Frame packing\" done");
+        if (!gst_h264_write_sei_frame_packing (&nw,
+                &msg->payload.frame_packing)) {
+          GST_WARNING ("Failed to write \"Frame packing\"");
+          goto error;
+        }
+        have_written_data = TRUE;
+        break;
+      case GST_H264_SEI_MASTERING_DISPLAY_COLOUR_VOLUME:
+        GST_DEBUG ("Wrtiting \"Mastering display colour volume\"");
+        if (!gst_h264_write_sei_mastering_display_colour_volume (&nw,
+                &msg->payload.mastering_display_colour_volume)) {
+          GST_WARNING ("Failed to write \"Mastering display colour volume\"");
+          goto error;
+        }
+        have_written_data = TRUE;
+        break;
+      case GST_H264_SEI_CONTENT_LIGHT_LEVEL:
+        GST_DEBUG ("Writing \"Content light level\" done");
+        if (!gst_h264_write_sei_content_light_level_info (&nw,
+                &msg->payload.content_light_level)) {
+          GST_WARNING ("Failed to write \"Content light level\"");
+          goto error;
+        }
+        have_written_data = TRUE;
+        break;
+      default:
+        break;
+    }
+
+    if (need_align && !nal_writer_do_rbsp_trailing_bits (&nw)) {
+      GST_WARNING ("Cannot insert traling bits");
+      goto error;
+    }
+  }
+
+  if (!have_written_data) {
+    GST_WARNING ("No written sei data");
+    goto error;
+  }
+
+  if (!nal_writer_do_rbsp_trailing_bits (&nw)) {
+    GST_WARNING ("Failed to insert rbsp trailing bits");
+    goto error;
+  }
+
+  return nal_writer_reset_and_get_memory (&nw);
+
+error:
+  nal_writer_reset (&nw);
+
+  return NULL;
+}
+
+/**
+ * gst_h264_create_sei_memory:
+ * @start_code_prefix_length: a length of start code prefix, must be 3 or 4
+ * @messages: (transfer none): a GArray of #GstH264SEIMessage
+ *
+ * Creates raw byte-stream format (a.k.a Annex B type) SEI nal unit data
+ * from @messages
+ *
+ * Returns: a #GstMemory containing a SEI nal unit
+ *
+ * Since: 1.18
+ */
+GstMemory *
+gst_h264_create_sei_memory (guint8 start_code_prefix_length, GArray * messages)
+{
+  g_return_val_if_fail (start_code_prefix_length == 3
+      || start_code_prefix_length == 4, NULL);
+  g_return_val_if_fail (messages != NULL, NULL);
+  g_return_val_if_fail (messages->len > 0, NULL);
+
+  return gst_h264_create_sei_memory_internal (start_code_prefix_length,
+      FALSE, messages);
+}
+
+/**
+ * gst_h264_create_sei_memory_avc:
+ * @nal_length_size: a size of nal length field, allowed range is [1, 4]
+ * @messages: (transfer none): a GArray of #GstH264SEIMessage
+ *
+ * Creates raw packetized format SEI nal unit data from @messages
+ *
+ * Returns: a #GstMemory containing a SEI nal unit
+ *
+ * Since: 1.18
+ */
+GstMemory *
+gst_h264_create_sei_memory_avc (guint8 nal_length_size, GArray * messages)
+{
+  g_return_val_if_fail (nal_length_size > 0 && nal_length_size < 5, NULL);
+  g_return_val_if_fail (messages != NULL, NULL);
+  g_return_val_if_fail (messages->len > 0, NULL);
+
+  return gst_h264_create_sei_memory_internal (nal_length_size, TRUE, messages);
+}
+
+static GstBuffer *
+gst_h264_parser_insert_sei_internal (GstH264NalParser * nalparser,
+    guint8 nal_prefix_size, gboolean packetized, GstBuffer * au,
+    GstMemory * sei)
+{
+  GstH264NalUnit nalu;
+  GstMapInfo info;
+  GstH264ParserResult pres;
+  guint offset = 0;
+  GstBuffer *new_buffer = NULL;
+
+  if (!gst_buffer_map (au, &info, GST_MAP_READ)) {
+    GST_ERROR ("Cannot map au buffer");
+    return NULL;
+  }
+
+  /* Find the offset of the first slice */
+  do {
+    if (packetized) {
+      pres = gst_h264_parser_identify_nalu_avc (nalparser,
+          info.data, offset, info.size, nal_prefix_size, &nalu);
+    } else {
+      pres = gst_h264_parser_identify_nalu (nalparser,
+          info.data, offset, info.size, &nalu);
+    }
+
+    if (pres != GST_H264_PARSER_OK && pres != GST_H264_PARSER_NO_NAL_END) {
+      GST_DEBUG ("Failed to identify nal unit, ret: %d", pres);
+      gst_buffer_unmap (au, &info);
+
+      return NULL;
+    }
+
+    if ((nalu.type >= GST_H264_NAL_SLICE && nalu.type <= GST_H264_NAL_SLICE_IDR)
+        || (nalu.type >= GST_H264_NAL_SLICE_AUX
+            && nalu.type <= GST_H264_NAL_SLICE_DEPTH)) {
+      GST_DEBUG ("Found slice nal type %d at offset %d",
+          nalu.type, nalu.sc_offset);
+      break;
+    }
+
+    offset = nalu.offset + nalu.size;
+  } while (pres == GST_H264_PARSER_OK);
+  gst_buffer_unmap (au, &info);
+
+  /* found the best position now, create new buffer */
+  new_buffer = gst_buffer_new ();
+
+  /* copy all metadata */
+  if (!gst_buffer_copy_into (new_buffer, au, GST_BUFFER_COPY_METADATA, 0, -1)) {
+    GST_ERROR ("Failed to copy metadata into new buffer");
+    gst_clear_buffer (&new_buffer);
+    goto out;
+  }
+
+  /* copy non-slice nal */
+  if (nalu.sc_offset > 0) {
+    if (!gst_buffer_copy_into (new_buffer, au,
+            GST_BUFFER_COPY_MEMORY, 0, nalu.sc_offset)) {
+      GST_ERROR ("Failed to copy buffer");
+      gst_clear_buffer (&new_buffer);
+      goto out;
+    }
+  }
+
+  /* insert sei */
+  gst_buffer_append_memory (new_buffer, gst_memory_ref (sei));
+
+  /* copy the rest */
+  if (!gst_buffer_copy_into (new_buffer, au,
+          GST_BUFFER_COPY_MEMORY, nalu.sc_offset, -1)) {
+    GST_ERROR ("Failed to copy buffer");
+    gst_clear_buffer (&new_buffer);
+    goto out;
+  }
+
+out:
+  return new_buffer;
+}
+
+/**
+ * gst_h264_parser_insert_sei:
+ * @nalparser: a #GstH264NalParser
+ * @au: (transfer none): a #GstBuffer containing AU data
+ * @sei: (transfer none): a #GstMemory containing a SEI nal
+ *
+ * Copy @au into new #GstBuffer and insert @sei into the #GstBuffer.
+ * The validation for completeness of @au and @sei is caller's responsibility.
+ * Both @au and @sei must be byte-stream formatted
+ *
+ * Returns: (nullable): a SEI inserted #GstBuffer or %NULL
+ *   if cannot figure out proper position to insert a @sei
+ *
+ * Since: 1.18
+ */
+GstBuffer *
+gst_h264_parser_insert_sei (GstH264NalParser * nalparser, GstBuffer * au,
+    GstMemory * sei)
+{
+  g_return_val_if_fail (nalparser != NULL, NULL);
+  g_return_val_if_fail (GST_IS_BUFFER (au), NULL);
+  g_return_val_if_fail (sei != NULL, NULL);
+
+  /* the size of start code prefix (3 or 4) is not matter since it will be
+   * scanned */
+  return gst_h264_parser_insert_sei_internal (nalparser, 4, FALSE, au, sei);
+}
+
+/**
+ * gst_h264_parser_insert_sei_avc:
+ * @nalparser: a #GstH264NalParser
+ * @nal_length_size: a size of nal length field, allowed range is [1, 4]
+ * @au: (transfer none): a #GstBuffer containing AU data
+ * @sei: (transfer none): a #GstMemory containing a SEI nal
+ *
+ * Copy @au into new #GstBuffer and insert @sei into the #GstBuffer.
+ * The validation for completeness of @au and @sei is caller's responsibility.
+ * Nal prefix type of both @au and @sei must be packetized, and
+ * also the size of nal length field must be identical to @nal_length_size
+ *
+ * Returns: (nullable): a SEI inserted #GstBuffer or %NULL
+ *   if cannot figure out proper position to insert a @sei
+ *
+ * Since: 1.18
+ */
+GstBuffer *
+gst_h264_parser_insert_sei_avc (GstH264NalParser * nalparser,
+    guint8 nal_length_size, GstBuffer * au, GstMemory * sei)
+{
+  g_return_val_if_fail (nalparser != NULL, NULL);
+  g_return_val_if_fail (nal_length_size > 0 && nal_length_size < 5, NULL);
+  g_return_val_if_fail (GST_IS_BUFFER (au), NULL);
+  g_return_val_if_fail (sei != NULL, NULL);
+
+  /* the size of start code prefix (3 or 4) is not matter since it will be
+   * scanned */
+  return gst_h264_parser_insert_sei_internal (nalparser, nal_length_size, TRUE,
+      au, sei);
+}
index 7a8b4bc..bce0905 100644 (file)
@@ -1223,6 +1223,25 @@ GST_CODEC_PARSERS_API
 void gst_h264_video_calculate_framerate (const GstH264SPS * sps, guint field_pic_flag,
     guint pic_struct, gint * fps_num, gint * fps_den);
 
+GST_CODEC_PARSERS_API
+GstMemory * gst_h264_create_sei_memory (guint8 start_code_prefix_size,
+                                        GArray * messages);
+
+GST_CODEC_PARSERS_API
+GstMemory * gst_h264_create_sei_memory_avc (guint8 nal_length_size,
+                                            GArray * messages);
+
+GST_CODEC_PARSERS_API
+GstBuffer * gst_h264_parser_insert_sei (GstH264NalParser * nalparser,
+                                        GstBuffer * au,
+                                        GstMemory * sei);
+
+GST_CODEC_PARSERS_API
+GstBuffer * gst_h264_parser_insert_sei_avc (GstH264NalParser * nalparser,
+                                            guint8 nal_length_size,
+                                            GstBuffer * au,
+                                            GstMemory * sei);
+
 G_END_DECLS
 
 #endif
index 06bda1b..e9a8fd3 100644 (file)
@@ -245,6 +245,40 @@ static guint8 nalu_chained_sei[] = {
   0x06, 0x01, 0xc4, 0x80
 };
 
+/* Content light level information SEI message */
+static guint8 h264_sei_cll[] = {
+  0x00, 0x00, 0x00, 0x01, 0x06, 0x90, 0x04, 0x03, 0xe8, 0x01, 0x90, 0x80
+};
+
+/* Mastering display colour volume information SEI message */
+static guint8 h264_sei_mdcv[] = {
+  0x00, 0x00, 0x00, 0x01, 0x06, 0x89, 0x18, 0x84,
+  0xd0, 0x3e, 0x80, 0x33, 0x90, 0x86, 0xc4, 0x1d,
+  0x4c, 0x0b, 0xb8, 0x3d, 0x13, 0x40, 0x42, 0x00,
+  0x98, 0x96, 0x80, 0x00, 0x00, 0x03, 0x00, 0x01,
+  0x80
+};
+
+/* closed caption data */
+static guint8 h264_sei_user_data_registered[] = {
+  0x00, 0x00, 0x00, 0x01, 0x06, 0x04, 0x47, 0xb5, 0x00, 0x31, 0x47, 0x41,
+  0x39, 0x34, 0x03, 0xd4,
+  0xff, 0xfc, 0x80, 0x80, 0xfd, 0x80, 0x80, 0xfa, 0x00, 0x00, 0xfa, 0x00,
+  0x00, 0xfa, 0x00, 0x00,
+  0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
+  0xfa, 0x00, 0x00, 0xfa,
+  0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
+  0x00, 0x00, 0xfa, 0x00,
+  0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00,
+  0x00, 0xff, 0x80
+};
+
+/* frame packing, side-by-side */
+static guint8 h264_sei_frame_packing[] = {
+  0x00, 0x00, 0x00, 0x01, 0x06, 0x2d, 0x07, 0x81, 0x81, 0x00, 0x00, 0x03,
+  0x00, 0x01, 0x20, 0x80
+};
+
 GST_START_TEST (test_h264_parse_invalid_sei)
 {
   GstH264ParserResult res;
@@ -313,6 +347,215 @@ GST_START_TEST (test_h264_parse_invalid_sei)
 
 GST_END_TEST;
 
+typedef gboolean (*SEICheckFunc) (gconstpointer a, gconstpointer b);
+
+static gboolean
+check_sei_user_data_registered (const GstH264RegisteredUserData * a,
+    const GstH264RegisteredUserData * b)
+{
+  if (a->country_code != b->country_code)
+    return FALSE;
+
+  if ((a->country_code == 0xff) &&
+      (a->country_code_extension != b->country_code_extension))
+    return FALSE;
+
+  if (a->size != b->size)
+    return FALSE;
+
+  return !memcmp (a->data, b->data, a->size);
+}
+
+static gboolean
+check_sei_frame_packing (const GstH264FramePacking * a,
+    const GstH264FramePacking * b)
+{
+  if ((a->frame_packing_id != b->frame_packing_id) ||
+      (a->frame_packing_cancel_flag != b->frame_packing_cancel_flag))
+    return FALSE;
+
+  if (!a->frame_packing_cancel_flag) {
+    if ((a->frame_packing_type != b->frame_packing_type) ||
+        (a->quincunx_sampling_flag != b->quincunx_sampling_flag) ||
+        (a->content_interpretation_type != b->content_interpretation_type) ||
+        (a->spatial_flipping_flag != b->spatial_flipping_flag) ||
+        (a->frame0_flipped_flag != b->frame0_flipped_flag) ||
+        (a->field_views_flag != b->field_views_flag) ||
+        (a->current_frame_is_frame0_flag != b->current_frame_is_frame0_flag) ||
+        (a->frame0_self_contained_flag != b->frame0_self_contained_flag) ||
+        (a->frame1_self_contained_flag != b->frame1_self_contained_flag))
+      return FALSE;
+
+    if (!a->quincunx_sampling_flag &&
+        a->frame_packing_type != GST_H264_FRAME_PACKING_TEMPORAL_INTERLEAVING) {
+      if ((a->frame0_grid_position_x != b->frame0_grid_position_x) ||
+          (a->frame0_grid_position_y != b->frame0_grid_position_y) ||
+          (a->frame1_grid_position_x != b->frame1_grid_position_x) ||
+          (a->frame1_grid_position_y != b->frame1_grid_position_y))
+        return FALSE;
+    }
+
+    if (a->frame_packing_repetition_period !=
+        b->frame_packing_repetition_period)
+      return FALSE;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+check_sei_mdcv (const GstH264MasteringDisplayColourVolume * a,
+    const GstH264MasteringDisplayColourVolume * b)
+{
+  gint i;
+  for (i = 0; i < 3; i++) {
+    if (a->display_primaries_x[i] != b->display_primaries_x[i] ||
+        a->display_primaries_y[i] != b->display_primaries_y[i])
+      return FALSE;
+  }
+
+  return (a->white_point_x == b->white_point_x) &&
+      (a->white_point_y == b->white_point_y) &&
+      (a->max_display_mastering_luminance == b->max_display_mastering_luminance)
+      && (a->min_display_mastering_luminance ==
+      b->min_display_mastering_luminance);
+}
+
+static gboolean
+check_sei_cll (const GstH264ContentLightLevel * a,
+    const GstH264ContentLightLevel * b)
+{
+  return (a->max_content_light_level == b->max_content_light_level) &&
+      (a->max_pic_average_light_level == b->max_pic_average_light_level);
+}
+
+GST_START_TEST (test_h264_create_sei)
+{
+  GstH264NalParser *parser;
+  GstH264ParserResult parse_ret;
+  GstH264NalUnit nalu;
+  GArray *msg_array = NULL;
+  GstMemory *mem;
+  gint i;
+  GstMapInfo info;
+  struct
+  {
+    guint8 *raw_data;
+    guint len;
+    GstH264SEIPayloadType type;
+    GstH264SEIMessage parsed_message;
+    SEICheckFunc check_func;
+  } test_list[] = {
+    /* *INDENT-OFF* */
+    {h264_sei_user_data_registered, G_N_ELEMENTS (h264_sei_user_data_registered),
+        GST_H264_SEI_REGISTERED_USER_DATA, {0,},
+        (SEICheckFunc) check_sei_user_data_registered},
+    {h264_sei_frame_packing, G_N_ELEMENTS (h264_sei_frame_packing),
+        GST_H264_SEI_FRAME_PACKING, {0,},
+        (SEICheckFunc) check_sei_frame_packing},
+    {h264_sei_mdcv, G_N_ELEMENTS (h264_sei_mdcv),
+        GST_H264_SEI_MASTERING_DISPLAY_COLOUR_VOLUME, {0,},
+        (SEICheckFunc) check_sei_mdcv},
+    {h264_sei_cll, G_N_ELEMENTS (h264_sei_cll),
+        GST_H264_SEI_CONTENT_LIGHT_LEVEL, {0,},
+        (SEICheckFunc) check_sei_cll},
+    /* *INDENT-ON* */
+  };
+
+  parser = gst_h264_nal_parser_new ();
+
+  /* test single sei message per sei nal unit */
+  for (i = 0; i < G_N_ELEMENTS (test_list); i++) {
+    gsize nal_size;
+
+    parse_ret = gst_h264_parser_identify_nalu_unchecked (parser,
+        test_list[i].raw_data, 0, test_list[i].len, &nalu);
+    assert_equals_int (parse_ret, GST_H264_PARSER_OK);
+    assert_equals_int (nalu.type, GST_H264_NAL_SEI);
+
+    parse_ret = gst_h264_parser_parse_sei (parser, &nalu, &msg_array);
+    assert_equals_int (parse_ret, GST_H264_PARSER_OK);
+    assert_equals_int (msg_array->len, 1);
+
+    /* test bytestream */
+    mem = gst_h264_create_sei_memory (4, msg_array);
+    fail_unless (mem != NULL);
+    fail_unless (gst_memory_map (mem, &info, GST_MAP_READ));
+    GST_MEMDUMP ("created sei nal", info.data, info.size);
+    GST_MEMDUMP ("original sei nal", test_list[i].raw_data, test_list[i].len);
+    assert_equals_int (info.size, test_list[i].len);
+    fail_if (memcmp (info.data, test_list[i].raw_data, test_list[i].len));
+    gst_memory_unmap (mem, &info);
+    gst_memory_unref (mem);
+
+    /* test packetized */
+    mem = gst_h264_create_sei_memory_avc (4, msg_array);
+    fail_unless (mem != NULL);
+    fail_unless (gst_memory_map (mem, &info, GST_MAP_READ));
+    assert_equals_int (info.size, test_list[i].len);
+    fail_if (memcmp (info.data + 4, test_list[i].raw_data + 4,
+            test_list[i].len - 4));
+    nal_size = GST_READ_UINT32_BE (info.data);
+    assert_equals_int (nal_size, info.size - 4);
+    gst_memory_unmap (mem, &info);
+    gst_memory_unref (mem);
+
+    /* store parsed SEI for following tests */
+    test_list[i].parsed_message =
+        g_array_index (msg_array, GstH264SEIMessage, 0);
+    if (test_list[i].type == GST_H264_SEI_REGISTERED_USER_DATA) {
+      GstH264RegisteredUserData *dst_rud =
+          &test_list[i].parsed_message.payload.registered_user_data;
+      const GstH264SEIMessage *src_msg =
+          &g_array_index (msg_array, GstH264SEIMessage, 0);
+      const GstH264RegisteredUserData *src_rud =
+          &src_msg->payload.registered_user_data;
+
+      dst_rud->data = g_malloc (src_rud->size);
+      memcpy ((guint8 *) dst_rud->data, src_rud->data, src_rud->size);
+    }
+    g_array_unref (msg_array);
+  }
+
+  /* test multiple SEI messages in a nal unit */
+  msg_array = g_array_new (FALSE, FALSE, sizeof (GstH264SEIMessage));
+  for (i = 0; i < G_N_ELEMENTS (test_list); i++)
+    g_array_append_val (msg_array, test_list[i].parsed_message);
+
+  mem = gst_h264_create_sei_memory (4, msg_array);
+  fail_unless (mem != NULL);
+  g_array_unref (msg_array);
+
+  /* parse sei message from buffer */
+  fail_unless (gst_memory_map (mem, &info, GST_MAP_READ));
+  parse_ret = gst_h264_parser_identify_nalu_unchecked (parser,
+      info.data, 0, info.size, &nalu);
+  assert_equals_int (parse_ret, GST_H264_PARSER_OK);
+  assert_equals_int (nalu.type, GST_H264_NAL_SEI);
+  parse_ret = gst_h264_parser_parse_sei (parser, &nalu, &msg_array);
+  gst_memory_unmap (mem, &info);
+  gst_memory_unref (mem);
+
+  assert_equals_int (parse_ret, GST_H264_PARSER_OK);
+  assert_equals_int (msg_array->len, G_N_ELEMENTS (test_list));
+  for (i = 0; i < msg_array->len; i++) {
+    GstH264SEIMessage *msg = &g_array_index (msg_array, GstH264SEIMessage, i);
+
+    assert_equals_int (msg->payloadType, test_list[i].type);
+    fail_unless (test_list[i].check_func (&msg->payload,
+            &test_list[i].parsed_message.payload));
+  }
+
+  /* clean up */
+  for (i = 0; i < G_N_ELEMENTS (test_list); i++)
+    gst_h264_sei_clear (&test_list[i].parsed_message);
+
+  g_array_unref (msg_array);
+  gst_h264_nal_parser_free (parser);
+}
+
+GST_END_TEST;
+
 static Suite *
 h264parser_suite (void)
 {
@@ -325,6 +568,7 @@ h264parser_suite (void)
   tcase_add_test (tc_chain, test_h264_parse_slice_eoseq_slice);
   tcase_add_test (tc_chain, test_h264_parse_slice_5bytes);
   tcase_add_test (tc_chain, test_h264_parse_invalid_sei);
+  tcase_add_test (tc_chain, test_h264_create_sei);
 
   return s;
 }