h265parser: Add a new NAL parsing API to handle malformed packets
authorSeungha Yang <seungha@centricular.com>
Mon, 9 May 2022 18:32:42 +0000 (03:32 +0900)
committerSeungha Yang <seungha@centricular.com>
Mon, 9 May 2022 18:58:51 +0000 (03:58 +0900)
Add gst_h265_parser_identify_and_split_nalu_hevc() method to
handle a case where packetized stream contains start-code prefix.
This new method behaves similar to exisiting gst_h265_parser_identify_nalu_hevc()
but it will scan start-code prefix to split given data into
NAL units.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2394>

subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth265parser.c
subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth265parser.h
subprojects/gst-plugins-bad/tests/check/libs/h265parser.c

index bcc4d84..95bf642 100644 (file)
@@ -1582,6 +1582,179 @@ gst_h265_parser_identify_nalu_hevc (GstH265Parser * parser,
 }
 
 /**
+ * gst_h265_parser_identify_and_split_nalu_hevc:
+ * @parser: a #GstH265Parser
+ * @data: The data to parse, must be the beging of the Nal unit
+ * @offset: the offset from which to parse @data
+ * @size: the size of @data
+ * @nal_length_size: the size in bytes of the HEVC nal length prefix.
+ * @nalus: a caller allocated GArray of #GstH265NalUnit where to store parsed nal headers
+ * @consumed: the size of consumed bytes
+ *
+ * Parses @data for packetized (e.g., hvc1/hev1) bitstream and
+ * sets @nalus. In addition to nal identifying process,
+ * this method scans start-code prefix to split malformed packet into
+ * actual nal chunks.
+ *
+ * Returns: a #GstH265ParserResult
+ *
+ * Since: 1.22
+ */
+GstH265ParserResult
+gst_h265_parser_identify_and_split_nalu_hevc (GstH265Parser * parser,
+    const guint8 * data, guint offset, gsize size, guint8 nal_length_size,
+    GArray * nalus, gsize * consumed)
+{
+  GstBitReader br;
+  guint nalu_size;
+  guint remaining;
+  guint off;
+  guint sc_size;
+
+  g_return_val_if_fail (data != NULL, GST_H265_PARSER_ERROR);
+  g_return_val_if_fail (nalus != NULL, GST_H265_PARSER_ERROR);
+  g_return_val_if_fail (nal_length_size > 0 && nal_length_size < 5,
+      GST_H265_PARSER_ERROR);
+
+  g_array_set_size (nalus, 0);
+
+  if (consumed)
+    *consumed = 0;
+
+  /* Would overflow guint below otherwise: the callers needs to ensure that
+   * this never happens */
+  if (offset > G_MAXUINT32 - nal_length_size) {
+    GST_WARNING ("offset + nal_length_size overflow");
+    return GST_H265_PARSER_BROKEN_DATA;
+  }
+
+  if (size < offset + nal_length_size) {
+    GST_DEBUG ("Can't parse, buffer has too small size %" G_GSIZE_FORMAT
+        ", offset %u", size, offset);
+    return GST_H265_PARSER_ERROR;
+  }
+
+  /* Read nal unit size and unwrap the size field */
+  gst_bit_reader_init (&br, data + offset, size - offset);
+  nalu_size = gst_bit_reader_get_bits_uint32_unchecked (&br,
+      nal_length_size * 8);
+
+  if (nalu_size < 2) {
+    GST_WARNING ("too small nal size %d", nalu_size);
+    return GST_H265_PARSER_BROKEN_DATA;
+  }
+
+  if (size < (gsize) nalu_size + nal_length_size) {
+    GST_WARNING ("larger nalu size %d than data size %" G_GSIZE_FORMAT,
+        nalu_size + nal_length_size, size);
+    return GST_H265_PARSER_BROKEN_DATA;
+  }
+
+  if (consumed)
+    *consumed = nalu_size + nal_length_size;
+
+  off = offset + nal_length_size;
+  remaining = nalu_size;
+  sc_size = nal_length_size;
+
+  /* Drop trailing start-code since it will not be scanned */
+  if (remaining >= 3) {
+    if (data[off + remaining - 1] == 0x01 && data[off + remaining - 2] == 0x00
+        && data[off + remaining - 3] == 0x00) {
+      remaining -= 3;
+
+      /* 4 bytes start-code */
+      if (remaining > 0 && data[off + remaining - 1] == 0x00)
+        remaining--;
+    }
+  }
+
+  /* Looping to split malformed nal units. nal-length field was dropped above
+   * so expected bitstream structure are:
+   *
+   * <complete nalu>
+   * | nalu |
+   * sc scan result will be -1 and handled in CONDITION-A
+   *
+   * <nalu with startcode prefix>
+   * | SC | nalu |
+   * Hit CONDITION-C first then terminated in CONDITION-A
+   *
+   * <first nal has no startcode but others have>
+   * | nalu | SC | nalu | ...
+   * CONDITION-B handles those cases
+   */
+  do {
+    GstH265NalUnit nalu;
+    gint sc_offset = -1;
+    guint skip_size = 0;
+
+    memset (&nalu, 0, sizeof (GstH265NalUnit));
+
+    /* startcode 3 bytes + minimum nal size 2 */
+    if (remaining >= 5)
+      sc_offset = scan_for_start_codes (data + off, remaining);
+
+    if (sc_offset < 0) {
+      if (remaining >= 2) {
+        /* CONDITION-A */
+        /* Last chunk */
+        nalu.size = remaining;
+        nalu.sc_offset = off - sc_size;
+        nalu.offset = off;
+        nalu.data = (guint8 *) data;
+        nalu.valid = TRUE;
+
+        gst_h265_parse_nalu_header (&nalu);
+        g_array_append_val (nalus, nalu);
+      }
+      break;
+    } else if ((sc_offset == 2 && data[off + sc_offset - 1] != 0)
+        || sc_offset > 2) {
+      /* CONDITION-B */
+      /* Found trailing startcode prefix */
+
+      nalu.size = sc_offset;
+      if (data[off + sc_offset - 1] == 0) {
+        /* 4 bytes start code */
+        nalu.size--;
+      }
+
+      nalu.sc_offset = off - sc_size;
+      nalu.offset = off;
+      nalu.data = (guint8 *) data;
+      nalu.valid = TRUE;
+
+      gst_h265_parse_nalu_header (&nalu);
+      g_array_append_val (nalus, nalu);
+    } else {
+      /* CONDITION-C */
+      /* startcode located at beginning of this chunk without actual nal data.
+       * skip this start code */
+    }
+
+    skip_size = sc_offset + 3;
+    if (skip_size >= remaining)
+      break;
+
+    /* no more nal-length bytes but 3bytes startcode */
+    sc_size = 3;
+    if (sc_offset > 0 && data[off + sc_offset - 1] == 0)
+      sc_size++;
+
+    remaining -= skip_size;
+    off += skip_size;
+  } while (remaining >= 2);
+
+  if (nalus->len > 0)
+    return GST_H265_PARSER_OK;
+
+  GST_WARNING ("No nal found");
+
+  return GST_H265_PARSER_BROKEN_DATA;
+}
+
+/**
  * gst_h265_parser_parse_nal:
  * @parser: a #GstH265Parser
  * @nalu: The #GstH265NalUnit to parse
index ba94ac7..ca2e91b 100644 (file)
@@ -1654,6 +1654,15 @@ GstH265ParserResult gst_h265_parser_identify_nalu_hevc (GstH265Parser  * parser,
                                                         GstH265NalUnit * nalu);
 
 GST_CODEC_PARSERS_API
+GstH265ParserResult gst_h265_parser_identify_and_split_nalu_hevc (GstH265Parser * parser,
+                                                                  const guint8 * data,
+                                                                  guint offset,
+                                                                  gsize size,
+                                                                  guint8 nal_length_size,
+                                                                  GArray * nalus,
+                                                                  gsize * consumed);
+
+GST_CODEC_PARSERS_API
 GstH265ParserResult gst_h265_parser_parse_nal       (GstH265Parser   * parser,
                                                      GstH265NalUnit  * nalu);
 
index 4d7ee8d..8065030 100644 (file)
@@ -1175,6 +1175,203 @@ GST_START_TEST (test_h265_create_sei)
 
 GST_END_TEST;
 
+GST_START_TEST (test_h265_split_hevc)
+{
+  GstH265Parser *parser;
+  GArray *array;
+  GstH265NalUnit *nal;
+  static const guint8 aud[] = { 0x46, 0x01, 0x10 };
+  static const guint8 eos[] = { 0x48, 0x01 };
+  static const guint8 sc_3bytes[] = { 0x00, 0x00, 0x01 };
+  static const guint8 sc_4bytes[] = { 0x00, 0x00, 0x00, 0x01 };
+  const guint8 nal_length_size = 4;
+  guint8 data[128];
+  gsize size;
+  GstH265ParserResult ret;
+  gsize consumed;
+  guint off;
+
+  parser = gst_h265_parser_new ();
+  array = g_array_new (FALSE, FALSE, sizeof (GstH265NalUnit));
+
+#define BUILD_NAL(arr) G_STMT_START { \
+  memcpy (data + off, arr, sizeof (arr)); \
+  off += sizeof (arr); \
+} G_STMT_END
+
+  /* 1) Complete packetized nalu */
+  size = nal_length_size + sizeof (aud);
+  off = nal_length_size;
+  GST_WRITE_UINT32_BE (data, sizeof (aud));
+  BUILD_NAL (aud);
+  ret = gst_h265_parser_identify_and_split_nalu_hevc (parser, data,
+      0, size, nal_length_size, array, &consumed);
+  assert_equals_int (ret, GST_H265_PARSER_OK);
+  assert_equals_int (array->len, 1);
+  assert_equals_int (consumed, size);
+  nal = &g_array_index (array, GstH265NalUnit, 0);
+  assert_equals_int (nal->type, GST_H265_NAL_AUD);
+  assert_equals_int (nal->sc_offset, 0);
+  assert_equals_int (nal->offset, nal_length_size);
+  assert_equals_int (nal->size, sizeof (aud));
+
+  /* 2-1) SC (3 bytes) + nalu */
+  size = nal_length_size + sizeof (sc_3bytes) + sizeof (aud);
+  off = nal_length_size;
+  GST_WRITE_UINT32_BE (data, sizeof (sc_3bytes) + sizeof (aud));
+  BUILD_NAL (sc_3bytes);
+  BUILD_NAL (aud);
+  ret = gst_h265_parser_identify_and_split_nalu_hevc (parser, data,
+      0, size, nal_length_size, array, &consumed);
+  assert_equals_int (ret, GST_H265_PARSER_OK);
+  assert_equals_int (array->len, 1);
+  assert_equals_int (consumed, size);
+  nal = &g_array_index (array, GstH265NalUnit, 0);
+  assert_equals_int (nal->type, GST_H265_NAL_AUD);
+  assert_equals_int (nal->sc_offset, nal_length_size);
+  assert_equals_int (nal->offset, nal_length_size + sizeof (sc_3bytes));
+  assert_equals_int (nal->size, sizeof (aud));
+
+  /* 2-2) SC (4 bytes) + nalu */
+  size = nal_length_size + sizeof (sc_4bytes) + sizeof (aud);
+  off = nal_length_size;
+  GST_WRITE_UINT32_BE (data, sizeof (sc_4bytes) + sizeof (aud));
+  BUILD_NAL (sc_4bytes);
+  BUILD_NAL (aud);
+  ret = gst_h265_parser_identify_and_split_nalu_hevc (parser, data,
+      0, size, nal_length_size, array, &consumed);
+  assert_equals_int (ret, GST_H265_PARSER_OK);
+  assert_equals_int (array->len, 1);
+  assert_equals_int (consumed, size);
+  nal = &g_array_index (array, GstH265NalUnit, 0);
+  assert_equals_int (nal->type, GST_H265_NAL_AUD);
+  assert_equals_int (nal->sc_offset, nal_length_size);
+  assert_equals_int (nal->offset, nal_length_size + sizeof (sc_4bytes));
+  assert_equals_int (nal->size, sizeof (aud));
+
+  /* 3-1) nalu + trailing SC (3 bytes) */
+  size = nal_length_size + sizeof (aud) + sizeof (sc_3bytes);
+  off = nal_length_size;
+  GST_WRITE_UINT32_BE (data, sizeof (aud) + sizeof (sc_3bytes));
+  BUILD_NAL (aud);
+  BUILD_NAL (sc_3bytes);
+  ret = gst_h265_parser_identify_and_split_nalu_hevc (parser, data,
+      0, size, nal_length_size, array, &consumed);
+  assert_equals_int (ret, GST_H265_PARSER_OK);
+  assert_equals_int (array->len, 1);
+  assert_equals_int (consumed, size);
+  nal = &g_array_index (array, GstH265NalUnit, 0);
+  assert_equals_int (nal->type, GST_H265_NAL_AUD);
+  assert_equals_int (nal->sc_offset, 0);
+  assert_equals_int (nal->offset, nal_length_size);
+  assert_equals_int (nal->size, sizeof (aud));
+
+  /* 3-2) nalu + trailing SC (4 bytes) */
+  size = nal_length_size + sizeof (aud) + sizeof (sc_4bytes);
+  off = nal_length_size;
+  GST_WRITE_UINT32_BE (data, sizeof (aud) + sizeof (sc_4bytes));
+  BUILD_NAL (aud);
+  BUILD_NAL (sc_4bytes);
+  ret = gst_h265_parser_identify_and_split_nalu_hevc (parser, data,
+      0, size, nal_length_size, array, &consumed);
+  assert_equals_int (ret, GST_H265_PARSER_OK);
+  assert_equals_int (array->len, 1);
+  assert_equals_int (consumed, size);
+  nal = &g_array_index (array, GstH265NalUnit, 0);
+  assert_equals_int (nal->type, GST_H265_NAL_AUD);
+  assert_equals_int (nal->sc_offset, 0);
+  assert_equals_int (nal->offset, nal_length_size);
+  assert_equals_int (nal->size, sizeof (aud));
+
+  /* 4-1) SC + nalu + SC + nalu */
+  size = nal_length_size + sizeof (sc_3bytes) + sizeof (aud) +
+      sizeof (sc_4bytes) + sizeof (eos);
+  off = nal_length_size;
+  GST_WRITE_UINT32_BE (data, sizeof (sc_3bytes) + sizeof (aud) +
+      sizeof (sc_4bytes) + sizeof (eos));
+  BUILD_NAL (sc_3bytes);
+  BUILD_NAL (aud);
+  BUILD_NAL (sc_4bytes);
+  BUILD_NAL (eos);
+  ret = gst_h265_parser_identify_and_split_nalu_hevc (parser, data,
+      0, size, nal_length_size, array, &consumed);
+  assert_equals_int (ret, GST_H265_PARSER_OK);
+  assert_equals_int (array->len, 2);
+  assert_equals_int (consumed, size);
+  nal = &g_array_index (array, GstH265NalUnit, 0);
+  assert_equals_int (nal->type, GST_H265_NAL_AUD);
+  assert_equals_int (nal->sc_offset, nal_length_size);
+  assert_equals_int (nal->offset, nal_length_size + sizeof (sc_3bytes));
+  assert_equals_int (nal->size, sizeof (aud));
+  nal = &g_array_index (array, GstH265NalUnit, 1);
+  assert_equals_int (nal->type, GST_H265_NAL_EOS);
+  assert_equals_int (nal->sc_offset, nal_length_size + sizeof (sc_3bytes)
+      + sizeof (aud));
+  assert_equals_int (nal->offset, nal_length_size + sizeof (sc_3bytes)
+      + sizeof (aud) + sizeof (sc_4bytes));
+  assert_equals_int (nal->size, sizeof (eos));
+
+  /* 4-2) SC + nalu + SC + nalu + trailing SC */
+  size = nal_length_size + sizeof (sc_3bytes) + sizeof (aud) +
+      sizeof (sc_4bytes) + sizeof (eos) + sizeof (sc_3bytes);
+  off = nal_length_size;
+  GST_WRITE_UINT32_BE (data, sizeof (sc_3bytes) + sizeof (aud) +
+      sizeof (sc_4bytes) + sizeof (eos) + sizeof (sc_3bytes));
+  BUILD_NAL (sc_3bytes);
+  BUILD_NAL (aud);
+  BUILD_NAL (sc_4bytes);
+  BUILD_NAL (eos);
+  BUILD_NAL (sc_3bytes);
+  ret = gst_h265_parser_identify_and_split_nalu_hevc (parser, data,
+      0, size, nal_length_size, array, &consumed);
+  assert_equals_int (ret, GST_H265_PARSER_OK);
+  assert_equals_int (array->len, 2);
+  assert_equals_int (consumed, size);
+  nal = &g_array_index (array, GstH265NalUnit, 0);
+  assert_equals_int (nal->type, GST_H265_NAL_AUD);
+  assert_equals_int (nal->sc_offset, nal_length_size);
+  assert_equals_int (nal->offset, nal_length_size + sizeof (sc_3bytes));
+  assert_equals_int (nal->size, sizeof (aud));
+  nal = &g_array_index (array, GstH265NalUnit, 1);
+  assert_equals_int (nal->type, GST_H265_NAL_EOS);
+  assert_equals_int (nal->sc_offset, nal_length_size + sizeof (sc_3bytes)
+      + sizeof (aud));
+  assert_equals_int (nal->offset, nal_length_size + sizeof (sc_3bytes)
+      + sizeof (aud) + sizeof (sc_4bytes));
+  assert_equals_int (nal->size, sizeof (eos));
+
+  /* 4-3) nalu + SC + nalu */
+  size = nal_length_size + sizeof (aud) + sizeof (sc_4bytes) + sizeof (eos);
+  off = nal_length_size;
+  GST_WRITE_UINT32_BE (data, sizeof (aud) + sizeof (sc_4bytes) + sizeof (eos));
+  BUILD_NAL (aud);
+  BUILD_NAL (sc_4bytes);
+  BUILD_NAL (eos);
+  ret = gst_h265_parser_identify_and_split_nalu_hevc (parser, data,
+      0, size, nal_length_size, array, &consumed);
+  assert_equals_int (ret, GST_H265_PARSER_OK);
+  assert_equals_int (array->len, 2);
+  assert_equals_int (consumed, size);
+  nal = &g_array_index (array, GstH265NalUnit, 0);
+  assert_equals_int (nal->type, GST_H265_NAL_AUD);
+  assert_equals_int (nal->sc_offset, 0);
+  assert_equals_int (nal->offset, nal_length_size);
+  assert_equals_int (nal->size, sizeof (aud));
+  nal = &g_array_index (array, GstH265NalUnit, 1);
+  assert_equals_int (nal->type, GST_H265_NAL_EOS);
+  assert_equals_int (nal->sc_offset, nal_length_size + sizeof (aud));
+  assert_equals_int (nal->offset,
+      nal_length_size + sizeof (aud) + sizeof (sc_4bytes));
+  assert_equals_int (nal->size, sizeof (eos));
+
+#undef BUILD_NAL
+
+  gst_h265_parser_free (parser);
+  g_array_unref (array);
+}
+
+GST_END_TEST;
+
 static Suite *
 h265parser_suite (void)
 {
@@ -1197,6 +1394,7 @@ h265parser_suite (void)
   tcase_add_test (tc_chain, test_h265_nal_type_classification);
   tcase_add_test (tc_chain, test_h265_sei_registered_user_data);
   tcase_add_test (tc_chain, test_h265_create_sei);
+  tcase_add_test (tc_chain, test_h265_split_hevc);
 
   return s;
 }