diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth264parser.c b/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth264parser.c index 811d81845d..85119713eb 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth264parser.c +++ b/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth264parser.c @@ -1651,6 +1651,179 @@ gst_h264_parser_identify_nalu_avc (GstH264NalParser * nalparser, return GST_H264_PARSER_OK; } +/** + * gst_h264_parser_identify_and_split_nalu_avc: + * @nalparser: a #GstH264NalParser + * @data: The data to parse, containing an AVC coded NAL unit + * @offset: the offset in @data from which to parse the NAL unit + * @size: the size of @data + * @nal_length_size: the size in bytes of the AVC nal length prefix. + * @nalus: a caller allocated GArray of #GstH264NalUnit where to store parsed nal headers + * @consumed: (out): the size of consumed bytes + * + * Parses @data for packetized (e.g., avc/avc3) 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 #GstH264ParserResult + * + * Since: 1.22.9 + */ +GstH264ParserResult +gst_h264_parser_identify_and_split_nalu_avc (GstH264NalParser * nalparser, + 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_H264_PARSER_ERROR); + g_return_val_if_fail (nalus != NULL, GST_H264_PARSER_ERROR); + g_return_val_if_fail (nal_length_size > 0 && nal_length_size < 5, + GST_H264_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_H264_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_H264_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 < 1) { + GST_WARNING ("too small nal size %d", nalu_size); + return GST_H264_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_H264_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 { + GstH264NalUnit nalu; + gint sc_offset = -1; + guint skip_size = 0; + + memset (&nalu, 0, sizeof (GstH264NalUnit)); + + /* startcode 3 bytes + minimum nal size 1 */ + if (remaining >= 4) + sc_offset = scan_for_start_codes (data + off, remaining); + + if (sc_offset < 0) { + if (remaining >= 1) { + /* CONDITION-A */ + /* Last chunk */ + nalu.size = remaining; + nalu.sc_offset = off - sc_size; + nalu.offset = off; + nalu.data = (guint8 *) data; + nalu.valid = TRUE; + + gst_h264_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_h264_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 >= 1); + + if (nalus->len > 0) + return GST_H264_PARSER_OK; + + GST_WARNING ("No nal found"); + + return GST_H264_PARSER_BROKEN_DATA; +} + /** * gst_h264_parser_parse_nal: * @nalparser: a #GstH264NalParser diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth264parser.h b/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth264parser.h index e6b956a8ab..2b0b667c73 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth264parser.h +++ b/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth264parser.h @@ -1440,6 +1440,15 @@ GstH264ParserResult gst_h264_parser_identify_nalu_avc (GstH264NalParser *nalpars guint offset, gsize size, guint8 nal_length_size, GstH264NalUnit *nalu); +GST_CODEC_PARSERS_API +GstH264ParserResult gst_h264_parser_identify_and_split_nalu_avc (GstH264NalParser *nalparser, + const guint8 *data, + guint offset, + gsize size, + guint8 nal_length_size, + GArray * nalus, + gsize * consumed); + GST_CODEC_PARSERS_API GstH264ParserResult gst_h264_parser_parse_nal (GstH264NalParser *nalparser, GstH264NalUnit *nalu); diff --git a/subprojects/gst-plugins-bad/tests/check/libs/h264parser.c b/subprojects/gst-plugins-bad/tests/check/libs/h264parser.c index 33676ebfe9..f42fd31ac5 100644 --- a/subprojects/gst-plugins-bad/tests/check/libs/h264parser.c +++ b/subprojects/gst-plugins-bad/tests/check/libs/h264parser.c @@ -878,6 +878,203 @@ GST_START_TEST (test_h264_parse_partial_nal_header) GST_END_TEST; +GST_START_TEST (test_h264_split_avc) +{ + GstH264NalParser *parser; + GArray *array; + GstH264NalUnit *nal; + static const guint8 aud[] = { 0x09, 0xf0 }; + static const guint8 seq_end[] = { 0x0a }; + 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; + GstH264ParserResult ret; + gsize consumed; + guint off; + + parser = gst_h264_nal_parser_new (); + array = g_array_new (FALSE, FALSE, sizeof (GstH264NalUnit)); + +#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_h264_parser_identify_and_split_nalu_avc (parser, data, + 0, size, nal_length_size, array, &consumed); + assert_equals_int (ret, GST_H264_PARSER_OK); + assert_equals_int (array->len, 1); + assert_equals_int (consumed, size); + nal = &g_array_index (array, GstH264NalUnit, 0); + assert_equals_int (nal->type, GST_H264_NAL_AU_DELIMITER); + 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_h264_parser_identify_and_split_nalu_avc (parser, data, + 0, size, nal_length_size, array, &consumed); + assert_equals_int (ret, GST_H264_PARSER_OK); + assert_equals_int (array->len, 1); + assert_equals_int (consumed, size); + nal = &g_array_index (array, GstH264NalUnit, 0); + assert_equals_int (nal->type, GST_H264_NAL_AU_DELIMITER); + 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_h264_parser_identify_and_split_nalu_avc (parser, data, + 0, size, nal_length_size, array, &consumed); + assert_equals_int (ret, GST_H264_PARSER_OK); + assert_equals_int (array->len, 1); + assert_equals_int (consumed, size); + nal = &g_array_index (array, GstH264NalUnit, 0); + assert_equals_int (nal->type, GST_H264_NAL_AU_DELIMITER); + 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_h264_parser_identify_and_split_nalu_avc (parser, data, + 0, size, nal_length_size, array, &consumed); + assert_equals_int (ret, GST_H264_PARSER_OK); + assert_equals_int (array->len, 1); + assert_equals_int (consumed, size); + nal = &g_array_index (array, GstH264NalUnit, 0); + assert_equals_int (nal->type, GST_H264_NAL_AU_DELIMITER); + 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_h264_parser_identify_and_split_nalu_avc (parser, data, + 0, size, nal_length_size, array, &consumed); + assert_equals_int (ret, GST_H264_PARSER_OK); + assert_equals_int (array->len, 1); + assert_equals_int (consumed, size); + nal = &g_array_index (array, GstH264NalUnit, 0); + assert_equals_int (nal->type, GST_H264_NAL_AU_DELIMITER); + 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 (seq_end); + off = nal_length_size; + GST_WRITE_UINT32_BE (data, sizeof (sc_3bytes) + sizeof (aud) + + sizeof (sc_4bytes) + sizeof (seq_end)); + BUILD_NAL (sc_3bytes); + BUILD_NAL (aud); + BUILD_NAL (sc_4bytes); + BUILD_NAL (seq_end); + ret = gst_h264_parser_identify_and_split_nalu_avc (parser, data, + 0, size, nal_length_size, array, &consumed); + assert_equals_int (ret, GST_H264_PARSER_OK); + assert_equals_int (array->len, 2); + assert_equals_int (consumed, size); + nal = &g_array_index (array, GstH264NalUnit, 0); + assert_equals_int (nal->type, GST_H264_NAL_AU_DELIMITER); + 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, GstH264NalUnit, 1); + assert_equals_int (nal->type, GST_H264_NAL_SEQ_END); + 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 (seq_end)); + + /* 4-2) SC + nalu + SC + nalu + trailing SC */ + size = nal_length_size + sizeof (sc_3bytes) + sizeof (aud) + + sizeof (sc_4bytes) + sizeof (seq_end) + sizeof (sc_3bytes); + off = nal_length_size; + GST_WRITE_UINT32_BE (data, sizeof (sc_3bytes) + sizeof (aud) + + sizeof (sc_4bytes) + sizeof (seq_end) + sizeof (sc_3bytes)); + BUILD_NAL (sc_3bytes); + BUILD_NAL (aud); + BUILD_NAL (sc_4bytes); + BUILD_NAL (seq_end); + BUILD_NAL (sc_3bytes); + ret = gst_h264_parser_identify_and_split_nalu_avc (parser, data, + 0, size, nal_length_size, array, &consumed); + assert_equals_int (ret, GST_H264_PARSER_OK); + assert_equals_int (array->len, 2); + assert_equals_int (consumed, size); + nal = &g_array_index (array, GstH264NalUnit, 0); + assert_equals_int (nal->type, GST_H264_NAL_AU_DELIMITER); + 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, GstH264NalUnit, 1); + assert_equals_int (nal->type, GST_H264_NAL_SEQ_END); + 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 (seq_end)); + + /* 4-3) nalu + SC + nalu */ + size = nal_length_size + sizeof (aud) + sizeof (sc_4bytes) + sizeof (seq_end); + off = nal_length_size; + GST_WRITE_UINT32_BE (data, sizeof (aud) + sizeof (sc_4bytes) + + sizeof (seq_end)); + BUILD_NAL (aud); + BUILD_NAL (sc_4bytes); + BUILD_NAL (seq_end); + ret = gst_h264_parser_identify_and_split_nalu_avc (parser, data, + 0, size, nal_length_size, array, &consumed); + assert_equals_int (ret, GST_H264_PARSER_OK); + assert_equals_int (array->len, 2); + assert_equals_int (consumed, size); + nal = &g_array_index (array, GstH264NalUnit, 0); + assert_equals_int (nal->type, GST_H264_NAL_AU_DELIMITER); + 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, GstH264NalUnit, 1); + assert_equals_int (nal->type, GST_H264_NAL_SEQ_END); + 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 (seq_end)); + +#undef BUILD_NAL + + gst_h264_nal_parser_free (parser); + g_array_unref (array); +} + +GST_END_TEST; static Suite * h264parser_suite (void) @@ -895,6 +1092,7 @@ h264parser_suite (void) tcase_add_test (tc_chain, test_h264_create_sei); tcase_add_test (tc_chain, test_h264_decoder_config_record); tcase_add_test (tc_chain, test_h264_parse_partial_nal_header); + tcase_add_test (tc_chain, test_h264_split_avc); return s; }