h264parser: Add gst_h264_parser_identify_and_split_nalu_avc() method

Equivalent to _split_nalu_hevc() method in h265parser. This method
will scan start-code prefix and split into individual NAL units
if start-code prefix is detected

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5934>
This commit is contained in:
Seungha Yang 2023-12-06 21:23:08 +09:00 committed by GStreamer Marge Bot
parent 2122cf512a
commit 2d87560b14
3 changed files with 381 additions and 0 deletions

View file

@ -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:
*
* <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 {
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

View file

@ -1381,6 +1381,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);

View file

@ -825,6 +825,204 @@ GST_START_TEST (test_h264_decoder_config_record)
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)
{
@ -840,6 +1038,7 @@ h264parser_suite (void)
tcase_add_test (tc_chain, test_h264_parse_invalid_sei);
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_split_avc);
return s;
}