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]
