PR #24774 opened by xiujunwang-dev
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24774
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24774.patch

Fixes #24637

The hevc_mp4toannexb bitstream filter could prepend VPS/SPS/PPS parameter
sets from hvcC even when the same parameter sets were already present
in-band, resulting in duplicate parameter sets in the output stream.

Avoid inserting parameter sets that are already present in-band while
preserving the existing hvcC conversion behavior and declarative SEI NAL units.


>From cb01ef3f6a7c28a74ae02fbc8deb9a1392c12960 Mon Sep 17 00:00:00 2001
From: Xiujun Wang <[email protected]>
Date: Tue, 29 Sep 2026 21:44:27 +0800
Subject: [PATCH] avcodec/bsf/hevc_mp4toannexb: avoid duplicate in-band
 parameter sets

When an IRAP packet already contains VPS, SPS and PPS, do not prepend the
same parameter sets from hvcC. Keep inserting any declarative SEI NAL units
from hvcC and preserve the existing behavior for missing parameter sets.

Add an API test for these cases.

Fixes: #24637
Signed-off-by: Xiujun Wang <[email protected]>
---
 libavcodec/bsf/hevc_mp4toannexb.c     |  28 +++++-
 tests/api/Makefile                    |   1 +
 tests/api/api-hevc-mp4toannexb-test.c | 121 ++++++++++++++++++++++++++
 tests/fate/api.mak                    |   5 ++
 4 files changed, 152 insertions(+), 3 deletions(-)
 create mode 100644 tests/api/api-hevc-mp4toannexb-test.c

diff --git a/libavcodec/bsf/hevc_mp4toannexb.c 
b/libavcodec/bsf/hevc_mp4toannexb.c
index 79633499b4..ff73552215 100644
--- a/libavcodec/bsf/hevc_mp4toannexb.c
+++ b/libavcodec/bsf/hevc_mp4toannexb.c
@@ -36,6 +36,8 @@
 typedef struct HEVCBSFContext {
     uint8_t *extradata;
     size_t   extradata_size;
+    uint8_t *sei;
+    size_t   sei_size;
     uint8_t  length_size;
     int      extradata_parsed;
 } HEVCBSFContext;
@@ -51,6 +53,8 @@ static int hevc_extradata_to_annexb(AVBSFContext *ctx,
 
     uint8_t *new_extradata = NULL;
     size_t   new_extradata_size = 0;
+    uint8_t *new_sei = NULL;
+    size_t   new_sei_size = 0;
 
     bytestream2_init(&gb, extradata, extradata_size);
 
@@ -85,6 +89,15 @@ static int hevc_extradata_to_annexb(AVBSFContext *ctx,
 
             AV_WB32(new_extradata + new_extradata_size, 1); // add the 
startcode
             bytestream2_get_buffer(&gb, new_extradata + new_extradata_size + 
4, nalu_len);
+            if (type == HEVC_NAL_SEI_PREFIX || type == HEVC_NAL_SEI_SUFFIX) {
+                ret = av_reallocp(&new_sei, new_sei_size + nalu_len + 4 + 
AV_INPUT_BUFFER_PADDING_SIZE);
+                if (ret < 0)
+                    goto fail;
+                memcpy(new_sei + new_sei_size, new_extradata + 
new_extradata_size,
+                       nalu_len + 4);
+                new_sei_size += nalu_len + 4;
+                memset(new_sei + new_sei_size, 0, 
AV_INPUT_BUFFER_PADDING_SIZE);
+            }
             new_extradata_size += 4 + nalu_len;
             memset(new_extradata + new_extradata_size, 0, 
AV_INPUT_BUFFER_PADDING_SIZE);
         }
@@ -110,12 +123,16 @@ static int hevc_extradata_to_annexb(AVBSFContext *ctx,
     av_freep(&s->extradata);
     s->extradata = new_extradata;
     s->extradata_size = new_extradata_size;
+    av_freep(&s->sei);
+    s->sei = new_sei;
+    s->sei_size = new_sei_size;
 
     s->length_size = length_size;
     s->extradata_parsed = 1;
     return 0;
 fail:
     av_freep(&new_extradata);
+    av_freep(&new_sei);
     return ret;
 }
 
@@ -149,6 +166,7 @@ static int hevc_mp4toannexb_filter(AVBSFContext *ctx, 
AVPacket *out)
 
     int got_irap = 0;
     int got_ps = 0, seen_irap_ps = 0;
+    unsigned int ps_mask = 0;
     int i, ret = 0;
     size_t extradata_size = 0;
     uint8_t *extradata = NULL;
@@ -198,7 +216,9 @@ static int hevc_mp4toannexb_filter(AVBSFContext *ctx, 
AVPacket *out)
         bytestream2_skip(&gb, nalu_size);
         got_irap |= nalu_type >= HEVC_NAL_BLA_W_LP &&
                     nalu_type <= HEVC_NAL_RSV_IRAP_VCL23;
-        got_ps   |= nalu_type >= HEVC_NAL_VPS && nalu_type <= HEVC_NAL_PPS;
+        got_ps |= nalu_type >= HEVC_NAL_VPS && nalu_type <= HEVC_NAL_PPS;
+        if (nalu_type >= HEVC_NAL_VPS && nalu_type <= HEVC_NAL_PPS)
+            ps_mask |= 1U << (nalu_type - HEVC_NAL_VPS);
     }
     seen_irap_ps = got_irap && got_ps;
     got_irap = got_ps = 0;
@@ -229,7 +249,7 @@ static int hevc_mp4toannexb_filter(AVBSFContext *ctx, 
AVPacket *out)
                   nalu_type <= HEVC_NAL_RSV_IRAP_VCL23;
         is_ps   = nalu_type >= HEVC_NAL_VPS && nalu_type <= HEVC_NAL_PPS && 
seen_irap_ps;
         add_extradata = (is_ps || is_irap) && !got_ps && !got_irap;
-        extra_size    = add_extradata * s->extradata_size;
+        extra_size    = add_extradata * (ps_mask == 0x7 ? s->sei_size : 
s->extradata_size);
         got_irap     |= is_irap;
         got_ps       |= is_ps;
 
@@ -245,7 +265,8 @@ static int hevc_mp4toannexb_filter(AVBSFContext *ctx, 
AVPacket *out)
             goto fail;
 
         if (extra_size)
-            memcpy(out->data + prev_size, s->extradata, extra_size);
+            memcpy(out->data + prev_size,
+                   ps_mask == 0x7 ? s->sei : s->extradata, extra_size);
         AV_WB32(out->data + prev_size + extra_size, 1);
         bytestream2_get_buffer(&gb, out->data + prev_size + 4 + extra_size, 
nalu_size);
     }
@@ -266,6 +287,7 @@ static void hevc_mp4toannexb_close(AVBSFContext *ctx)
 {
     HEVCBSFContext *s = ctx->priv_data;
     av_freep(&s->extradata);
+    av_freep(&s->sei);
 }
 
 static const enum AVCodecID codec_ids[] = {
diff --git a/tests/api/Makefile b/tests/api/Makefile
index f2738d4cba..94f672a233 100644
--- a/tests/api/Makefile
+++ b/tests/api/Makefile
@@ -2,6 +2,7 @@ APITESTPROGS-$(call ALLYES, LIBASTCENC_ENCODER 
LIBASTCENC_DECODER) += api-astc-p
 APITESTPROGS-$(call ENCDEC, FLAC, FLAC) += api-flac
 APITESTPROGS-$(call DEMDEC, H264, H264) += api-h264
 APITESTPROGS-$(call DEMDEC, H264, H264) += api-h264-slice
+APITESTPROGS-$(CONFIG_HEVC_MP4TOANNEXB_BSF) += api-hevc-mp4toannexb
 APITESTPROGS-$(CONFIG_MOV_MUXER)        += api-movenc
 APITESTPROGS-$(CONFIG_MOVTEXT_ENCODER)  += api-movtext
 APITESTPROGS-yes += api-seek api-dump-stream-meta
diff --git a/tests/api/api-hevc-mp4toannexb-test.c 
b/tests/api/api-hevc-mp4toannexb-test.c
new file mode 100644
index 0000000000..d069b35aa0
--- /dev/null
+++ b/tests/api/api-hevc-mp4toannexb-test.c
@@ -0,0 +1,121 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "libavcodec/bsf.h"
+#include "libavcodec/hevc/hevc.h"
+#include "libavutil/mem.h"
+
+static int check_packet(const int *input_types, int input_count,
+                        const int *expected_types, int expected_count,
+                        int add_sei)
+{
+    AVBSFContext *bsf = NULL;
+    AVPacket *in = NULL, *out = NULL;
+    uint8_t hvcc[23 + 4 * 7] = { 1 };
+    uint8_t *p = hvcc + 23;
+    const int hvcc_size = 23 + (3 + add_sei) * 7;
+    int ret = 1;
+
+    hvcc[21] = 3; // four-byte NAL lengths
+    hvcc[22] = 3 + add_sei; // VPS, SPS, PPS, optional SEI arrays
+    for (int i = 0; i < 3 + add_sei; i++) {
+        const int type = i == 3 ? HEVC_NAL_SEI_PREFIX : HEVC_NAL_VPS + i;
+        *p++ = type;
+        *p++ = 0;
+        *p++ = 1;
+        *p++ = 0;
+        *p++ = 2;
+        *p++ = type << 1;
+        *p++ = 1;
+    }
+
+    if (av_bsf_alloc(av_bsf_get_by_name("hevc_mp4toannexb"), &bsf) < 0)
+        goto end;
+    bsf->par_in->codec_type = AVMEDIA_TYPE_VIDEO;
+    bsf->par_in->codec_id   = AV_CODEC_ID_HEVC;
+    bsf->par_in->extradata = av_mallocz(hvcc_size + 
AV_INPUT_BUFFER_PADDING_SIZE);
+    if (!bsf->par_in->extradata)
+        goto end;
+    memcpy(bsf->par_in->extradata, hvcc, hvcc_size);
+    bsf->par_in->extradata_size = hvcc_size;
+    if (av_bsf_init(bsf) < 0)
+        goto end;
+
+    in = av_packet_alloc();
+    out = av_packet_alloc();
+    if (!in || !out || av_new_packet(in, input_count * 6) < 0)
+        goto end;
+    for (int i = 0; i < input_count; i++) {
+        uint8_t *nal = in->data + i * 6;
+        nal[0] = nal[1] = nal[2] = 0;
+        nal[3] = 2;
+        nal[4] = input_types[i] << 1;
+        nal[5] = 1;
+    }
+
+    if (av_bsf_send_packet(bsf, in) < 0 || av_bsf_receive_packet(bsf, out) < 0)
+        goto end;
+    if (out->size != expected_count * 6)
+        goto end;
+    for (int i = 0; i < expected_count; i++) {
+        const uint8_t *nal = out->data + i * 6;
+        if (memcmp(nal, "\0\0\0\1", 4) ||
+            nal[4] != expected_types[i] << 1 || nal[5] != 1)
+            goto end;
+    }
+    ret = 0;
+
+end:
+    av_packet_free(&in);
+    av_packet_free(&out);
+    av_bsf_free(&bsf);
+    return ret;
+}
+
+int main(void)
+{
+    const int irap[] = { HEVC_NAL_IDR_W_RADL };
+    const int with_ps[] = { HEVC_NAL_VPS, HEVC_NAL_SPS,
+                            HEVC_NAL_PPS, HEVC_NAL_IDR_W_RADL };
+    const int with_sei[] = { HEVC_NAL_SEI_PREFIX, HEVC_NAL_VPS,
+                             HEVC_NAL_SPS, HEVC_NAL_PPS, HEVC_NAL_IDR_W_RADL };
+    const int missing_ps_with_sei[] = { HEVC_NAL_VPS, HEVC_NAL_SPS,
+                                        HEVC_NAL_PPS, HEVC_NAL_SEI_PREFIX,
+                                        HEVC_NAL_IDR_W_RADL };
+
+    if (check_packet(irap, 1, with_ps, 4, 0)) {
+        fprintf(stderr, "Missing HEVC parameter sets were not inserted\n");
+        return 1;
+    }
+    if (check_packet(with_ps, 4, with_ps, 4, 0)) {
+        fprintf(stderr, "In-band HEVC parameter sets were duplicated\n");
+        return 1;
+    }
+    if (check_packet(with_ps, 4, with_sei, 5, 1)) {
+        fprintf(stderr, "HEVC SEI was lost or parameter sets were 
duplicated\n");
+        return 1;
+    }
+    if (check_packet(irap, 1, missing_ps_with_sei, 5, 1)) {
+        fprintf(stderr, "HEVC extradata insertion order changed\n");
+        return 1;
+    }
+    return 0;
+}
diff --git a/tests/fate/api.mak b/tests/fate/api.mak
index a10edfe4da..a503aaaa86 100644
--- a/tests/fate/api.mak
+++ b/tests/fate/api.mak
@@ -7,6 +7,11 @@ fate-api-flac: $(APITESTSDIR)/api-flac-test$(EXESUF)
 fate-api-flac: CMD = run $(APITESTSDIR)/api-flac-test$(EXESUF)
 fate-api-flac: CMP = null
 
+FATE_API_LIBAVCODEC-$(CONFIG_HEVC_MP4TOANNEXB_BSF) += fate-api-hevc-mp4toannexb
+fate-api-hevc-mp4toannexb: $(APITESTSDIR)/api-hevc-mp4toannexb-test$(EXESUF)
+fate-api-hevc-mp4toannexb: CMD = run 
$(APITESTSDIR)/api-hevc-mp4toannexb-test$(EXESUF)
+fate-api-hevc-mp4toannexb: CMP = null
+
 FATE_API_LIBAVCODEC-$(CONFIG_MOVTEXT_ENCODER) += fate-api-movtext
 fate-api-movtext: $(APITESTSDIR)/api-movtext-test$(EXESUF)
 fate-api-movtext: CMD = run $(APITESTSDIR)/api-movtext-test$(EXESUF)
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to