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 8a781dc64c..25427adc0d 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth264parser.c +++ b/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth264parser.c @@ -3463,3 +3463,229 @@ gst_h264_parser_insert_sei_avc (GstH264NalParser * nalparser, return gst_h264_parser_insert_sei_internal (nalparser, nal_length_size, TRUE, au, sei); } + +static GstH264DecoderConfigRecord * +gst_h264_decoder_config_record_new (void) +{ + GstH264DecoderConfigRecord *config; + + config = g_new0 (GstH264DecoderConfigRecord, 1); + config->sps = g_array_new (FALSE, FALSE, sizeof (GstH264NalUnit)); + config->pps = g_array_new (FALSE, FALSE, sizeof (GstH264NalUnit)); + config->sps_ext = g_array_new (FALSE, FALSE, sizeof (GstH264NalUnit)); + + return config; +} + +/** + * gst_h264_decoder_config_record_free: + * @config: (nullable): a #GstH264DecoderConfigRecord data + * + * Free @config data + * + * Since: 1.22 + */ +void +gst_h264_decoder_config_record_free (GstH264DecoderConfigRecord * config) +{ + if (!config) + return; + + if (config->sps) + g_array_unref (config->sps); + + if (config->pps) + g_array_unref (config->pps); + + if (config->sps_ext) + g_array_unref (config->sps_ext); + + g_free (config); +} + +/** + * gst_h264_parser_parse_decoder_config_record: + * @nalparser: a #GstH264NalParser + * @data: the data to parse + * @size: the size of @data + * @config: (out): parsed #GstH264DecoderConfigRecord data + * + * Parses AVCDecoderConfigurationRecord data and fill into @config. + * The caller must free @config via gst_h264_decoder_config_record_free() + * + * This method does not parse SPS and PPS and therefore the caller needs to + * parse each NAL unit via appropriate parsing method. + * + * Returns: a #GstH264ParserResult + * + * Since: 1.22 + */ +GstH264ParserResult +gst_h264_parser_parse_decoder_config_record (GstH264NalParser * nalparser, + const guint8 * data, gsize size, GstH264DecoderConfigRecord ** config) +{ + GstH264DecoderConfigRecord *ret; + GstBitReader br; + GstH264ParserResult result = GST_H264_PARSER_OK; + guint8 num_sps, num_pps, i; + guint offset; + + g_return_val_if_fail (nalparser != NULL, GST_H264_PARSER_ERROR); + g_return_val_if_fail (data != NULL, GST_H264_PARSER_ERROR); + g_return_val_if_fail (config != NULL, GST_H264_PARSER_ERROR); + +#define READ_CONFIG_UINT8(val, nbits) G_STMT_START { \ + if (!gst_bit_reader_get_bits_uint8 (&br, &val, nbits)) { \ + GST_WARNING ("Failed to read " G_STRINGIFY (val)); \ + result = GST_H264_PARSER_ERROR; \ + goto error; \ + } \ +} G_STMT_END; + +#define SKIP_CONFIG_BITS(nbits) G_STMT_START { \ + if (!gst_bit_reader_skip (&br, nbits)) { \ + GST_WARNING ("Failed to skip %d bits", nbits); \ + result = GST_H264_PARSER_ERROR; \ + goto error; \ + } \ +} G_STMT_END; + + *config = NULL; + + if (size < 7) { + GST_WARNING ("Too small size avcC"); + return GST_H264_PARSER_ERROR; + } + + gst_bit_reader_init (&br, data, size); + + ret = gst_h264_decoder_config_record_new (); + + READ_CONFIG_UINT8 (ret->configuration_version, 8); + /* Keep parsing, caller can decide whether this data needs to be discarded + * or not */ + if (ret->configuration_version != 1) { + GST_WARNING ("Wrong configurationVersion %d", ret->configuration_version); + result = GST_H264_PARSER_ERROR; + goto error; + } + + READ_CONFIG_UINT8 (ret->profile_indication, 8); + READ_CONFIG_UINT8 (ret->profile_compatibility, 8); + READ_CONFIG_UINT8 (ret->level_indication, 8); + /* reserved 6bits */ + SKIP_CONFIG_BITS (6); + READ_CONFIG_UINT8 (ret->length_size_minus_one, 2); + if (ret->length_size_minus_one == 2) { + /* "length_size_minus_one + 1" should be 1, 2, or 4 */ + GST_WARNING ("Wrong nal-length-size"); + result = GST_H264_PARSER_ERROR; + goto error; + } + + /* reserved 3bits */ + SKIP_CONFIG_BITS (3); + + READ_CONFIG_UINT8 (num_sps, 5); + offset = gst_bit_reader_get_pos (&br); + + g_assert (offset % 8 == 0); + offset /= 8; + for (i = 0; i < num_sps; i++) { + GstH264NalUnit nalu; + + result = gst_h264_parser_identify_nalu_avc (nalparser, + data, offset, size, 2, &nalu); + if (result != GST_H264_PARSER_OK) + goto error; + + g_array_append_val (ret->sps, nalu); + offset = nalu.offset + nalu.size; + } + + if (!gst_bit_reader_set_pos (&br, offset * 8)) { + result = GST_H264_PARSER_ERROR; + goto error; + } + + READ_CONFIG_UINT8 (num_pps, 8); + offset = gst_bit_reader_get_pos (&br); + + g_assert (offset % 8 == 0); + offset /= 8; + for (i = 0; i < num_pps; i++) { + GstH264NalUnit nalu; + + result = gst_h264_parser_identify_nalu_avc (nalparser, + data, offset, size, 2, &nalu); + if (result != GST_H264_PARSER_OK) + goto error; + + g_array_append_val (ret->pps, nalu); + offset = nalu.offset + nalu.size; + } + + /* Parse chroma format and SPS ext data. We will silently ignore any + * error while parsing below data since it's not essential data for + * decoding */ + if (ret->profile_indication == 100 || ret->profile_indication == 110 || + ret->profile_indication == 122 || ret->profile_indication == 144) { + guint8 num_sps_ext; + + if (!gst_bit_reader_set_pos (&br, offset * 8)) + goto out; + + if (!gst_bit_reader_skip (&br, 6)) + goto out; + + if (!gst_bit_reader_get_bits_uint8 (&br, &ret->chroma_format, 2)) + goto out; + + if (!gst_bit_reader_skip (&br, 5)) + goto out; + + if (!gst_bit_reader_get_bits_uint8 (&br, &ret->bit_depth_luma_minus8, 3)) + goto out; + + if (!gst_bit_reader_skip (&br, 5)) + goto out; + + if (!gst_bit_reader_get_bits_uint8 (&br, &ret->bit_depth_chroma_minus8, 3)) + goto out; + + if (!gst_bit_reader_get_bits_uint8 (&br, &num_sps_ext, 8)) + goto out; + + offset = gst_bit_reader_get_pos (&br); + + g_assert (offset % 8 == 0); + offset /= 8; + for (i = 0; i < num_sps_ext; i++) { + GstH264NalUnit nalu; + + result = gst_h264_parser_identify_nalu_avc (nalparser, + data, offset, size, 2, &nalu); + if (result != GST_H264_PARSER_OK) + goto out; + + g_array_append_val (ret->sps_ext, nalu); + offset = nalu.offset + nalu.size; + } + + ret->chroma_format_present = TRUE; + } + +out: + { + *config = ret; + return GST_H264_PARSER_OK; + } +error: + { + gst_h264_decoder_config_record_free (ret); + return result; + } + +#undef READ_CONFIG_UINT8 +#undef SKIP_CONFIG_BITS +} 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 3f76874a92..05273f23b5 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth264parser.h +++ b/subprojects/gst-plugins-bad/gst-libs/gst/codecparsers/gsth264parser.h @@ -375,6 +375,7 @@ typedef struct _GstH264MasteringDisplayColourVolume GstH264MasteringDisplayColou typedef struct _GstH264ContentLightLevel GstH264ContentLightLevel; typedef struct _GstH264SEIUnhandledPayload GstH264SEIUnhandledPayload; typedef struct _GstH264SEIMessage GstH264SEIMessage; +typedef struct _GstH264DecoderConfigRecord GstH264DecoderConfigRecord; /** * GstH264NalUnitExtensionMVC: @@ -1238,6 +1239,106 @@ struct _GstH264SEIMessage } payload; }; +/** + * GstH264DecoderConfigRecord: + * + * Contains AVCDecoderConfigurationRecord data as defined in ISO/IEC 14496-15 + * + * Since: 1.22 + */ +struct _GstH264DecoderConfigRecord +{ + /** + * GstH264DecoderConfigRecord.configuration_version: + * + * Indicates configurationVersion, must be 1 + */ + guint8 configuration_version; + + /** + * GstH264DecoderConfigRecord.profile_indication: + * + * H.264 profile indication + */ + guint8 profile_indication; + + /** + * GstH264DecoderConfigRecord.profile_compatibility: + * + * H.264 profile compatibility + */ + guint8 profile_compatibility; + + /** + * GstH264DecoderConfigRecord.level_indication: + * + * H.264 level indiction + */ + guint8 level_indication; + + /** + * GstH264DecoderConfigRecord.length_size_minus_one: + * + * Indicates the length in bytes of the NAL unit length field + */ + guint8 length_size_minus_one; + + /** + * GstH264DecoderConfigRecord.sps + * + * Array of identified #GstH264NalUnit from sequenceParameterSetNALUnit. + * This array may contain non-SPS nal units such as SEI message + */ + GArray *sps; + + /** + * GstH264DecoderConfigRecord.pps + * + * Array of identified #GstH264NalUnit from pictureParameterSetNALUnit. + * This array may contain non-PPS nal units such as SEI message + */ + GArray *pps; + + /** + * GstH264DecoderConfigRecord.chroma_format_present + * + * %TRUE if chroma information is present. Otherwise below values + * have no meaning + */ + gboolean chroma_format_present; + + /** + * GstH264DecoderConfigRecord.chroma_format + * + * chroma_format_idc defined in ISO/IEC 14496-10 + */ + guint8 chroma_format; + + /** + * GstH264DecoderConfigRecord.bit_depth_luma_minus8 + * + * Indicates bit depth of luma component + */ + guint8 bit_depth_luma_minus8; + + /** + * GstH264DecoderConfigRecord.bit_depth_chroma_minus8 + * + * Indicates bit depth of chroma component + */ + guint8 bit_depth_chroma_minus8; + + /** + * GstH264DecoderConfigRecord.sps_ext + * + * Array of identified #GstH264NalUnit from sequenceParameterSetExtNALUnit. + */ + GArray *sps_ext; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + /** * GstH264NalParser: * @@ -1366,6 +1467,15 @@ GstBuffer * gst_h264_parser_insert_sei_avc (GstH264NalParser * nalparser, GstBuffer * au, GstMemory * sei); +GST_CODEC_PARSERS_API +void gst_h264_decoder_config_record_free (GstH264DecoderConfigRecord * config); + +GST_CODEC_PARSERS_API +GstH264ParserResult gst_h264_parser_parse_decoder_config_record (GstH264NalParser * nalparser, + const guint8 * data, + gsize size, + GstH264DecoderConfigRecord ** config); + G_END_DECLS #endif diff --git a/subprojects/gst-plugins-bad/tests/check/libs/h264parser.c b/subprojects/gst-plugins-bad/tests/check/libs/h264parser.c index d322dd8db0..17238bafaf 100644 --- a/subprojects/gst-plugins-bad/tests/check/libs/h264parser.c +++ b/subprojects/gst-plugins-bad/tests/check/libs/h264parser.c @@ -714,6 +714,117 @@ GST_START_TEST (test_h264_create_sei) GST_END_TEST; +static guint8 h264_avc_codec_data[] = { + 0x01, 0x4d, 0x40, 0x15, 0xff, 0xe1, 0x00, 0x17, + 0x67, 0x4d, 0x40, 0x15, 0xec, 0xa4, 0xbf, 0x2e, + 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x2e, 0xe6, + 0xb2, 0x80, 0x01, 0xe2, 0xc5, 0xb2, 0xc0, 0x01, + 0x00, 0x04, 0x68, 0xeb, 0xec, 0xb2 +}; + +/* *INDENT-OFF* */ +static guint8 h264_avc3_codec_data[] = { + 0x01, /* config version, always == 1 */ + 0x4d, /* profile */ + 0x40, /* profile compatibility */ + 0x15, /* level */ + 0xff, /* 6 reserved bits, lengthSizeMinusOne */ + 0xe0, /* 3 reserved bits, numSPS */ + 0x00 /* numPPS */ +}; + +static guint8 h264_wrong_version_codec_data[] = { + 0x00, /* config version, wrong value 0 */ + 0x4d, /* profile */ + 0x40, /* profile compatibility */ + 0x15, /* level */ + 0xff, /* 6 reserved bits, lengthSizeMinusOne */ + 0xe0, /* 3 reserved bits, numSPS */ + 0x00 /* numPPS */ +}; + +static guint8 h264_wrong_length_size_codec_data[] = { + 0x01, /* config version, always == 1 */ + 0x4d, /* profile */ + 0x40, /* profile compatibility */ + 0x15, /* level */ + 0xfe, /* 6 reserved bits, invalid lengthSizeMinusOne 3 */ + 0xe0, /* 3 reserved bits, numSPS */ + 0x00 /* numPPS */ +}; +/* *INDENT-ON* */ + +GST_START_TEST (test_h264_decoder_config_record) +{ + GstH264NalParser *parser; + GstH264ParserResult ret; + GstH264DecoderConfigRecord *config = NULL; + GstH264SPS sps; + GstH264PPS pps; + GstH264NalUnit *nalu; + + parser = gst_h264_nal_parser_new (); + + /* avc */ + ret = gst_h264_parser_parse_decoder_config_record (parser, + h264_avc_codec_data, sizeof (h264_avc_codec_data), &config); + assert_equals_int (ret, GST_H264_PARSER_OK); + fail_unless (config != NULL); + assert_equals_int (config->configuration_version, 1); + assert_equals_int (config->length_size_minus_one, 3); + + assert_equals_int (config->sps->len, 1); + nalu = &g_array_index (config->sps, GstH264NalUnit, 0); + assert_equals_int (nalu->type, GST_H264_NAL_SPS); + ret = gst_h264_parser_parse_sps (parser, nalu, &sps); + assert_equals_int (ret, GST_H264_PARSER_OK); + gst_h264_sps_clear (&sps); + + assert_equals_int (config->pps->len, 1); + nalu = &g_array_index (config->pps, GstH264NalUnit, 0); + assert_equals_int (nalu->type, GST_H264_NAL_PPS); + ret = gst_h264_parser_parse_pps (parser, nalu, &pps); + assert_equals_int (ret, GST_H264_PARSER_OK); + gst_h264_pps_clear (&pps); + g_clear_pointer (&config, gst_h264_decoder_config_record_free); + + /* avc3 */ + ret = gst_h264_parser_parse_decoder_config_record (parser, + h264_avc3_codec_data, sizeof (h264_avc3_codec_data), &config); + assert_equals_int (ret, GST_H264_PARSER_OK); + fail_unless (config != NULL); + + assert_equals_int (config->configuration_version, 1); + assert_equals_int (config->length_size_minus_one, 3); + assert_equals_int (config->sps->len, 0); + assert_equals_int (config->pps->len, 0); + g_clear_pointer (&config, gst_h264_decoder_config_record_free); + + /* avc3 wrong size, return error with null config data */ + ret = gst_h264_parser_parse_decoder_config_record (parser, + h264_avc3_codec_data, sizeof (h264_avc3_codec_data) - 1, &config); + assert_equals_int (ret, GST_H264_PARSER_ERROR); + fail_unless (config == NULL); + + /* wrong version, return error with null config data */ + ret = gst_h264_parser_parse_decoder_config_record (parser, + h264_wrong_version_codec_data, sizeof (h264_wrong_version_codec_data), + &config); + assert_equals_int (ret, GST_H264_PARSER_ERROR); + fail_unless (config == NULL); + + /* wrong length size, return error with null config data */ + ret = gst_h264_parser_parse_decoder_config_record (parser, + h264_wrong_length_size_codec_data, + sizeof (h264_wrong_length_size_codec_data), &config); + assert_equals_int (ret, GST_H264_PARSER_ERROR); + fail_unless (config == NULL); + + gst_h264_nal_parser_free (parser); +} + +GST_END_TEST; + static Suite * h264parser_suite (void) { @@ -728,6 +839,7 @@ h264parser_suite (void) tcase_add_test (tc_chain, test_h264_parse_identify_nalu_avc); 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); return s; }