From a696b288861a09403e316f4eb33bbc7cb6c03e5c Mon Sep 17 00:00:00 2001 From: James Almer Date: Fri, 14 Jun 2024 18:24:40 -0300 Subject: [PATCH] avformat/hevc: don't write NALUs with nuh_layer_id > 0 in hvcC boxes Signed-off-by: James Almer --- libavformat/hevc.c | 140 ++++++++++++++++----- libavformat/hevc.h | 3 +- tests/ref/fate/enhanced-flv-hevc | 4 +- tests/ref/fate/matroska-dovi-write-config8 | 4 +- tests/ref/lavf-fate/hevc.flv | 2 +- 5 files changed, 118 insertions(+), 35 deletions(-) diff --git a/libavformat/hevc.c b/libavformat/hevc.c index d6b9d233d9..651c3b4b1d 100644 --- a/libavformat/hevc.c +++ b/libavformat/hevc.c @@ -40,12 +40,15 @@ enum { NB_ARRAYS }; +#define FLAG_ARRAY_COMPLETENESS (1 << 0) +#define FLAG_IS_NALFF (1 << 1) + typedef struct HVCCNALUnitArray { uint8_t array_completeness; uint8_t NAL_unit_type; uint16_t numNalus; uint16_t *nalUnitLength; - uint8_t **nalUnit; + const uint8_t **nalUnit; } HVCCNALUnitArray; typedef struct HEVCDecoderConfigurationRecord { @@ -654,24 +657,26 @@ static int hvcc_parse_pps(GetBitContext *gb, return 0; } -static void nal_unit_parse_header(GetBitContext *gb, uint8_t *nal_type) +static void nal_unit_parse_header(GetBitContext *gb, uint8_t *nal_type, + uint8_t *nuh_layer_id) { skip_bits1(gb); // forbidden_zero_bit *nal_type = get_bits(gb, 6); + *nuh_layer_id = get_bits(gb, 6); /* - * nuh_layer_id u(6) * nuh_temporal_id_plus1 u(3) */ - skip_bits(gb, 9); + skip_bits(gb, 3); } -static int hvcc_array_add_nal_unit(uint8_t *nal_buf, uint32_t nal_size, - uint8_t nal_type, int ps_array_completeness, +static int hvcc_array_add_nal_unit(const uint8_t *nal_buf, uint32_t nal_size, + uint8_t nal_type, int flags, HVCCNALUnitArray *array) { int ret; + int ps_array_completeness = !!(flags & FLAG_ARRAY_COMPLETENESS); uint16_t numNalus = array->numNalus; ret = av_reallocp_array(&array->nalUnit, numNalus + 1, sizeof(uint8_t*)); @@ -699,14 +704,14 @@ static int hvcc_array_add_nal_unit(uint8_t *nal_buf, uint32_t nal_size, return 0; } -static int hvcc_add_nal_unit(uint8_t *nal_buf, uint32_t nal_size, - int ps_array_completeness, +static int hvcc_add_nal_unit(const uint8_t *nal_buf, uint32_t nal_size, HEVCDecoderConfigurationRecord *hvcc, - unsigned array_idx) + int flags, unsigned array_idx) { int ret = 0; + int is_nalff = !!(flags & FLAG_IS_NALFF); GetBitContext gbc; - uint8_t nal_type; + uint8_t nal_type, nuh_layer_id; uint8_t *rbsp_buf; uint32_t rbsp_size; @@ -720,7 +725,9 @@ static int hvcc_add_nal_unit(uint8_t *nal_buf, uint32_t nal_size, if (ret < 0) goto end; - nal_unit_parse_header(&gbc, &nal_type); + nal_unit_parse_header(&gbc, &nal_type, &nuh_layer_id); + if (nuh_layer_id > 0) + goto end; /* * Note: only 'declarative' SEI messages are allowed in @@ -728,12 +735,17 @@ static int hvcc_add_nal_unit(uint8_t *nal_buf, uint32_t nal_size, * and non-declarative SEI messages discarded? */ ret = hvcc_array_add_nal_unit(nal_buf, nal_size, nal_type, - ps_array_completeness, + flags, &hvcc->arrays[array_idx]); if (ret < 0) goto end; if (hvcc->arrays[array_idx].numNalus == 1) hvcc->numOfArrays++; + + /* Don't parse parameter sets. We already have the needed information*/ + if (is_nalff) + goto end; + if (nal_type == HEVC_NAL_VPS) ret = hvcc_parse_vps(&gbc, hvcc); else if (nal_type == HEVC_NAL_SPS) @@ -1041,20 +1053,100 @@ int ff_hevc_annexb2mp4_buf(const uint8_t *buf_in, uint8_t **buf_out, return 0; } +static int hvcc_parse_nal_unit(const uint8_t *buf, uint32_t len, int type, + HEVCDecoderConfigurationRecord *hvcc, + int flags) +{ + for (unsigned i = 0; i < FF_ARRAY_ELEMS(hvcc->arrays); i++) { + static const uint8_t array_idx_to_type[] = + { HEVC_NAL_VPS, HEVC_NAL_SPS, HEVC_NAL_PPS, + HEVC_NAL_SEI_PREFIX, HEVC_NAL_SEI_SUFFIX }; + + if (type == array_idx_to_type[i]) { + int ret = hvcc_add_nal_unit(buf, len, hvcc, flags, i); + if (ret < 0) + return ret; + break; + } + } + + return 0; +} + int ff_isom_write_hvcc(AVIOContext *pb, const uint8_t *data, int size, int ps_array_completeness) { HEVCDecoderConfigurationRecord hvcc; - uint8_t *buf, *end, *start; + uint8_t *buf, *end, *start = NULL; + int flags = !!ps_array_completeness * FLAG_ARRAY_COMPLETENESS; int ret; if (size < 6) { /* We can't write a valid hvcC from the provided data */ return AVERROR_INVALIDDATA; } else if (*data == 1) { - /* Data is already hvcC-formatted */ - avio_write(pb, data, size); - return 0; + /* Data is already hvcC-formatted. Parse the arrays to skip any NALU + with nuh_layer_id > 0 */ + GetBitContext gbc; + int num_arrays; + + if (size < 23) + return AVERROR_INVALIDDATA; + + ret = init_get_bits8(&gbc, data, size); + if (ret < 0) + return ret; + + hvcc_init(&hvcc); + skip_bits(&gbc, 8); // hvcc.configurationVersion + hvcc.general_profile_space = get_bits(&gbc, 2); + hvcc.general_tier_flag = get_bits1(&gbc); + hvcc.general_profile_idc = get_bits(&gbc, 5); + hvcc.general_profile_compatibility_flags = get_bits_long(&gbc, 32); + hvcc.general_constraint_indicator_flags = get_bits64(&gbc, 48); + hvcc.general_level_idc = get_bits(&gbc, 8); + skip_bits(&gbc, 4); // reserved + hvcc.min_spatial_segmentation_idc = get_bits(&gbc, 12); + skip_bits(&gbc, 6); // reserved + hvcc.parallelismType = get_bits(&gbc, 2); + skip_bits(&gbc, 6); // reserved + hvcc.chromaFormat = get_bits(&gbc, 2); + skip_bits(&gbc, 5); // reserved + hvcc.bitDepthLumaMinus8 = get_bits(&gbc, 3); + skip_bits(&gbc, 5); // reserved + hvcc.bitDepthChromaMinus8 = get_bits(&gbc, 3); + hvcc.avgFrameRate = get_bits(&gbc, 16); + hvcc.constantFrameRate = get_bits(&gbc, 2); + hvcc.numTemporalLayers = get_bits(&gbc, 3); + hvcc.temporalIdNested = get_bits1(&gbc); + hvcc.lengthSizeMinusOne = get_bits(&gbc, 2); + + flags |= FLAG_IS_NALFF; + + num_arrays = get_bits(&gbc, 8); + for (int i = 0; i < num_arrays; i++) { + int type, num_nalus; + + skip_bits(&gbc, 2); + type = get_bits(&gbc, 6); + num_nalus = get_bits(&gbc, 16); + for (int j = 0; j < num_nalus; j++) { + int len = get_bits(&gbc, 16); + + if (len > (get_bits_left(&gbc) / 8)) + goto end; + + ret = hvcc_parse_nal_unit(data + get_bits_count(&gbc) / 8, + len, type, &hvcc, flags); + if (ret < 0) + goto end; + + skip_bits_long(&gbc, len * 8); + } + } + + ret = hvcc_write(pb, &hvcc); + goto end; } else if (!(AV_RB24(data) == 1 || AV_RB32(data) == 1)) { /* Not a valid Annex B start code prefix */ return AVERROR_INVALIDDATA; @@ -1075,19 +1167,9 @@ int ff_isom_write_hvcc(AVIOContext *pb, const uint8_t *data, buf += 4; - for (unsigned i = 0; i < FF_ARRAY_ELEMS(hvcc.arrays); i++) { - static const uint8_t array_idx_to_type[] = - { HEVC_NAL_VPS, HEVC_NAL_SPS, HEVC_NAL_PPS, - HEVC_NAL_SEI_PREFIX, HEVC_NAL_SEI_SUFFIX }; - - if (type == array_idx_to_type[i]) { - ret = hvcc_add_nal_unit(buf, len, ps_array_completeness, - &hvcc, i); - if (ret < 0) - goto end; - break; - } - } + ret = hvcc_parse_nal_unit(buf, len, type, &hvcc, flags); + if (ret < 0) + goto end; buf += len; } diff --git a/libavformat/hevc.h b/libavformat/hevc.h index 0f56325c1c..cb66ac66ac 100644 --- a/libavformat/hevc.h +++ b/libavformat/hevc.h @@ -79,7 +79,8 @@ int ff_hevc_annexb2mp4_buf(const uint8_t *buf_in, uint8_t **buf_out, int *size, int filter_ps, int *ps_count); /** - * Writes HEVC extradata (parameter sets, declarative SEI NAL units) to the + * Writes HEVC extradata (parameter sets and declarative SEI NAL units with + * nuh_layer_id == 0, as a HEVCDecoderConfigurationRecord) to the * provided AVIOContext. * * If the extradata is Annex B format, it gets converted to hvcC format before diff --git a/tests/ref/fate/enhanced-flv-hevc b/tests/ref/fate/enhanced-flv-hevc index f011d38a30..f04905d06b 100644 --- a/tests/ref/fate/enhanced-flv-hevc +++ b/tests/ref/fate/enhanced-flv-hevc @@ -1,6 +1,6 @@ -0da54607064548fa1aae5695751f189c *tests/data/fate/enhanced-flv-hevc.flv +565cf155790db391137f81f619448477 *tests/data/fate/enhanced-flv-hevc.flv 3603038 tests/data/fate/enhanced-flv-hevc.flv -#extradata 0: 551, 0xa18acf66 +#extradata 0: 551, 0xb1ddcd66 #extradata 1: 2, 0x00340022 #tb 0: 1/1000 #media_type 0: video diff --git a/tests/ref/fate/matroska-dovi-write-config8 b/tests/ref/fate/matroska-dovi-write-config8 index 472cbed708..44ca015e0e 100644 --- a/tests/ref/fate/matroska-dovi-write-config8 +++ b/tests/ref/fate/matroska-dovi-write-config8 @@ -1,6 +1,6 @@ -0730145aa317d800cb4bde0e3a38bb8d *tests/data/fate/matroska-dovi-write-config8.matroska +3bd4b07d5af6153516e4c0e66a71c8c9 *tests/data/fate/matroska-dovi-write-config8.matroska 3600607 tests/data/fate/matroska-dovi-write-config8.matroska -#extradata 0: 551, 0xa18acf66 +#extradata 0: 551, 0xb1ddcd66 #extradata 1: 2, 0x00340022 #tb 0: 1/1000 #media_type 0: video diff --git a/tests/ref/lavf-fate/hevc.flv b/tests/ref/lavf-fate/hevc.flv index 1105d8eddb..e3962e0938 100644 --- a/tests/ref/lavf-fate/hevc.flv +++ b/tests/ref/lavf-fate/hevc.flv @@ -1,3 +1,3 @@ -39cf3df5fc3a9c50ab71a294f45663fe *tests/data/lavf-fate/lavf.hevc.flv +c9e8b5df15135d21bd2781558f32f269 *tests/data/lavf-fate/lavf.hevc.flv 11819 tests/data/lavf-fate/lavf.hevc.flv tests/data/lavf-fate/lavf.hevc.flv CRC=0xd29da885