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: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2394>
This commit is contained in:
Seungha Yang 2022-05-10 03:32:42 +09:00
parent c47255d148
commit be84fc23ca
3 changed files with 380 additions and 0 deletions

View file

@ -1581,6 +1581,179 @@ gst_h265_parser_identify_nalu_hevc (GstH265Parser * parser,
return GST_H265_PARSER_OK;
}
/**
* 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

View file

@ -1653,6 +1653,15 @@ GstH265ParserResult gst_h265_parser_identify_nalu_hevc (GstH265Parser * parser,
guint8 nal_length_size,
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);

View 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;
}