diff mbox series

[FFmpeg-devel,04/11] avformat/hevc: don't write NALUs with nuh_layer_id > 0 in hvcC boxes

Message ID 20240703212648.48483-4-jamrial@gmail.com
State New
Headers show
Series [FFmpeg-devel,01/11] avformat/mov: add support for lhvC box parsing | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 fail Make fate failed
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

James Almer July 3, 2024, 9:26 p.m. UTC
hvcC should only contain nuh_layer_id == 0 NALUs. Support for a box meant to
contain higher layer NALUs will be added in a following patch.
To achieve this, all sources are parsed and filtered, including hvcC source
that until now were propagated untouched. This is reflected in how the affected
reference files now have ps_array_completeness set to 0 instead of 1 for some
non-PS NALUs.

Signed-off-by: James Almer <jamrial@gmail.com>
---
 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 mbox series

Patch

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