From be84fc23ca2e9f4d4c878f1a3db8499822435c9e Mon Sep 17 00:00:00 2001 From: Seungha Yang Date: Tue, 10 May 2022 03:32:42 +0900 Subject: [PATCH] h265parser: Add a new NAL parsing API to handle malformed packets 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: --- .../gst-libs/gst/codecparsers/gsth265parser.c | 173 ++++++++++++++++++ .../gst-libs/gst/codecparsers/gsth265parser.h | 9 + .../gst-plugins-bad/tests/check/libs/h265parser.c | 198 +++++++++++++++++++++ 3 files changed, 380 insertions(+) diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth265parser.c b/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth265parser.c index bcc4d84..95bf642 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth265parser.c +++ b/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth265parser.c @@ -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: + * + * + * | nalu | + * sc scan result will be -1 and handled in CONDITION-A + * + * + * | SC | nalu | + * Hit CONDITION-C first then terminated in CONDITION-A + * + * + * | 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 diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth265parser.h b/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth265parser.h index ba94ac7..ca2e91b 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth265parser.h +++ b/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth265parser.h @@ -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); diff --git a/subprojects/gst-plugins-bad/tests/check/libs/h265parser.c b/subprojects/gst-plugins-bad/tests/check/libs/h265parser.c index 4d7ee8d..8065030 100644 --- a/subprojects/gst-plugins-bad/tests/check/libs/h265parser.c +++ b/subprojects/gst-plugins-bad/tests/check/libs/h265parser.c @@ -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; } -- 2.7.4