PR #23122 opened by Kacper Michajłow (kasper93) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23122 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23122.patch
Allows splitting interleaved BL+EL HEVC bitstream into separate streams. From c1a1faed938f909a75027cff2ea240c2e2718d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= <[email protected]> Date: Sat, 16 May 2026 18:40:32 +0200 Subject: [PATCH] avcodec/bsf: add dovi_split BSF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allows splitting interleaved BL+EL HEVC bitstream into separate streams. Signed-off-by: Kacper Michajłow <[email protected]> --- configure | 1 + doc/bitstream_filters.texi | 29 +++++ libavcodec/bitstream_filters.c | 1 + libavcodec/bsf/Makefile | 1 + libavcodec/bsf/dovi_split.c | 203 +++++++++++++++++++++++++++++++++ 5 files changed, 235 insertions(+) create mode 100644 libavcodec/bsf/dovi_split.c diff --git a/configure b/configure index 39a522e7e8..dd6378b4ff 100755 --- a/configure +++ b/configure @@ -3756,6 +3756,7 @@ av1_frame_merge_bsf_select="cbs_av1" av1_frame_split_bsf_select="cbs_av1" av1_metadata_bsf_select="cbs_av1" dovi_rpu_bsf_select="cbs_h265 cbs_av1 dovi_rpudec dovi_rpuenc" +dovi_split_bsf_select="hevcparse" dts2pts_bsf_select="cbs_h264 h264parse cbs_h265 hevc_parser" eac3_core_bsf_select="ac3_parser" eia608_to_smpte436m_bsf_select="smpte_436m" diff --git a/doc/bitstream_filters.texi b/doc/bitstream_filters.texi index 36474d7dbe..750ad649e8 100644 --- a/doc/bitstream_filters.texi +++ b/doc/bitstream_filters.texi @@ -124,6 +124,35 @@ that this level currently behaves the same as @samp{limited} in libavcodec. @end table @end table +@section dovi_split + +Split a Dolby Vision Profile 7 multi-layer HEVC bitstream. Profile 7 carries the +enhancement-layer HEVC bitstream interleaved inside the base-layer access units, +wrapped in user-unspecified NAL units of type 63 (UNSPEC63), and the RPU metadata +as a sibling user-unspecified NAL of type 62 (UNSPEC62). + +The output is always Annex-B framed regardless of the input format. + +@table @option +@item mode +Which Dolby Vision components to keep in the output bitstream. +@table @samp +@item bl +Base layer only: drop every UNSPEC63 (EL) and every UNSPEC62 (RPU). +The output is a plain HEVC stream with no Dolby Vision markers. This is the +default. +@item bl+rpu +Base layer with the RPU NAL kept. +@item el +Enhancement layer only: for every UNSPEC63 NAL, strip the two-byte outer NAL +header and emit the inner payload. The result is a standalone HEVC bitstream. +UNSPEC62 (RPU) is dropped. +@item el+rpu +Enhancement layer with the RPU NAL kept verbatim. Same as @samp{el}, but the +UNSPEC62 RPU NALs are also emitted alongside the unwrapped EL NALs. +@end table +@end table + @section dump_extra Add extradata to the beginning of the filtered packets except when diff --git a/libavcodec/bitstream_filters.c b/libavcodec/bitstream_filters.c index 150cca8939..9c9443e5b1 100644 --- a/libavcodec/bitstream_filters.c +++ b/libavcodec/bitstream_filters.c @@ -34,6 +34,7 @@ extern const FFBitStreamFilter ff_chomp_bsf; extern const FFBitStreamFilter ff_dump_extradata_bsf; extern const FFBitStreamFilter ff_dca_core_bsf; extern const FFBitStreamFilter ff_dovi_rpu_bsf; +extern const FFBitStreamFilter ff_dovi_split_bsf; extern const FFBitStreamFilter ff_dts2pts_bsf; extern const FFBitStreamFilter ff_dv_error_marker_bsf; extern const FFBitStreamFilter ff_eac3_core_bsf; diff --git a/libavcodec/bsf/Makefile b/libavcodec/bsf/Makefile index eb090090ef..6d85ea785b 100644 --- a/libavcodec/bsf/Makefile +++ b/libavcodec/bsf/Makefile @@ -23,6 +23,7 @@ OBJS-$(CONFIG_H264_REDUNDANT_PPS_BSF) += bsf/h264_redundant_pps.o OBJS-$(CONFIG_HAPQA_EXTRACT_BSF) += bsf/hapqa_extract.o OBJS-$(CONFIG_HEVC_METADATA_BSF) += bsf/h265_metadata.o OBJS-$(CONFIG_DOVI_RPU_BSF) += bsf/dovi_rpu.o +OBJS-$(CONFIG_DOVI_SPLIT_BSF) += bsf/dovi_split.o OBJS-$(CONFIG_HEVC_MP4TOANNEXB_BSF) += bsf/hevc_mp4toannexb.o OBJS-$(CONFIG_IMX_DUMP_HEADER_BSF) += bsf/imx_dump_header.o OBJS-$(CONFIG_LCEVC_METADATA_BSF) += bsf/lcevc_metadata.o diff --git a/libavcodec/bsf/dovi_split.c b/libavcodec/bsf/dovi_split.c new file mode 100644 index 0000000000..68d8777e19 --- /dev/null +++ b/libavcodec/bsf/dovi_split.c @@ -0,0 +1,203 @@ +/* + * 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 "libavutil/avassert.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" + +#include "bsf.h" +#include "bsf_internal.h" +#include "h2645_parse.h" + +#include "hevc/hevc.h" + +enum DOVISplitMode { + DOVI_SPLIT_BL = 0, + DOVI_SPLIT_BL_RPU = 1, + DOVI_SPLIT_EL = 2, + DOVI_SPLIT_EL_RPU = 3, +}; + +typedef struct DOVISplitContext { + const AVClass *class; + int mode; + + int nal_length_size; /* 0 means Annex-B input */ + H2645Packet pkt; +} DOVISplitContext; + +static int dovi_split_init(AVBSFContext *ctx) +{ + DOVISplitContext *s = ctx->priv_data; + const uint8_t *ed = ctx->par_in->extradata; + int ed_size = ctx->par_in->extradata_size; + + /* hvcC starts with configurationVersion=1 and is at least 23 bytes; + * otherwise the stream is Annex-B with in-band parameter sets. */ + if (ed_size >= 23 && ed[0] == 1 && + AV_RB24(ed) != 1 && AV_RB32(ed) != 1) + s->nal_length_size = (ed[21] & 3) + 1; + else + s->nal_length_size = 0; + + return 0; +} + +static void dovi_split_close(AVBSFContext *ctx) +{ + DOVISplitContext *s = ctx->priv_data; + ff_h2645_packet_uninit(&s->pkt); +} + +static int nal_is_kept(const DOVISplitContext *s, const H2645NAL *nal, + const uint8_t **payload, int *payload_size) +{ + const int keep_el = s->mode == DOVI_SPLIT_EL || s->mode == DOVI_SPLIT_EL_RPU; + const int keep_bl = s->mode == DOVI_SPLIT_BL || s->mode == DOVI_SPLIT_BL_RPU; + const int keep_rpu = s->mode == DOVI_SPLIT_BL_RPU || s->mode == DOVI_SPLIT_EL_RPU; + + switch (nal->type) { + case HEVC_NAL_UNSPEC63: + /* EL: keep only when extracting EL, strip two-bytes of outer NAL header */ + if (!keep_el || nal->raw_size <= 2) + return 0; + *payload = nal->raw_data + 2; + *payload_size = nal->raw_size - 2; + return 1; + case HEVC_NAL_UNSPEC62: + /* RPU: kept verbatim only when the selected mode opted in. */ + if (!keep_rpu) + return 0; + *payload = nal->raw_data; + *payload_size = nal->raw_size; + return 1; + default: + /* Anything else is a base-layer NAL. */ + if (!keep_bl) + return 0; + *payload = nal->raw_data; + *payload_size = nal->raw_size; + return 1; + } +} + +static int dovi_split_filter(AVBSFContext *ctx, AVPacket *out) +{ + DOVISplitContext *s = ctx->priv_data; + AVPacket *in = NULL; + AVBufferRef *out_buf = NULL; + uint8_t *dst; + size_t out_size = 0; + int kept_count = 0; + int flags = (s->nal_length_size ? H2645_FLAG_IS_NALFF : 0) | + H2645_FLAG_SMALL_PADDING; + int ret; + + ret = ff_bsf_get_packet(ctx, &in); + if (ret < 0) + return ret; + + ret = ff_h2645_packet_split(&s->pkt, in->data, in->size, ctx, + s->nal_length_size, AV_CODEC_ID_HEVC, flags); + if (ret < 0) + goto fail; + + for (int i = 0; i < s->pkt.nb_nals; i++) { + const uint8_t *payload; + int payload_size; + if (!nal_is_kept(s, &s->pkt.nals[i], &payload, &payload_size)) + continue; + /* 4-byte Annex-B start code per NAL. */ + out_size += 4 + payload_size; + kept_count++; + } + + if (!kept_count) { + ret = AVERROR(EAGAIN); + goto fail; + } + + out_buf = av_buffer_alloc(out_size + AV_INPUT_BUFFER_PADDING_SIZE); + if (!out_buf) { + ret = AVERROR(ENOMEM); + goto fail; + } + + dst = out_buf->data; + for (int i = 0; i < s->pkt.nb_nals; i++) { + const uint8_t *payload; + int payload_size; + if (!nal_is_kept(s, &s->pkt.nals[i], &payload, &payload_size)) + continue; + AV_WB32(dst, 1); + dst += 4; + memcpy(dst, payload, payload_size); + dst += payload_size; + } + memset(dst, 0, AV_INPUT_BUFFER_PADDING_SIZE); + av_assert0(dst == out_buf->data + out_size); + + ret = av_packet_copy_props(out, in); + if (ret < 0) + goto fail; + + out->buf = out_buf; + out->data = out_buf->data; + out->size = out_size; + out_buf = NULL; + +fail: + av_buffer_unref(&out_buf); + av_packet_free(&in); + if (ret < 0 && ret != AVERROR(EAGAIN)) + av_packet_unref(out); + return ret; +} + +#define OFFSET(x) offsetof(DOVISplitContext, x) +#define FLAGS (AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_BSF_PARAM) +static const AVOption dovi_split_options[] = { + { "mode", "Which Dolby Vision components to keep in the output bitstream", OFFSET(mode), AV_OPT_TYPE_INT, { .i64 = DOVI_SPLIT_BL }, DOVI_SPLIT_BL, DOVI_SPLIT_EL_RPU, FLAGS, .unit = "mode" }, + { "bl", "Base layer only", 0, AV_OPT_TYPE_CONST, { .i64 = DOVI_SPLIT_BL }, .flags = FLAGS, .unit = "mode" }, + { "bl+rpu", "Base layer with the RPU NAL", 0, AV_OPT_TYPE_CONST, { .i64 = DOVI_SPLIT_BL_RPU }, .flags = FLAGS, .unit = "mode" }, + { "el", "Enhancement layer only", 0, AV_OPT_TYPE_CONST, { .i64 = DOVI_SPLIT_EL }, .flags = FLAGS, .unit = "mode" }, + { "el+rpu", "Enhancement layer with the RPU NAL", 0, AV_OPT_TYPE_CONST, { .i64 = DOVI_SPLIT_EL_RPU }, .flags = FLAGS, .unit = "mode" }, + { NULL }, +}; + +static const AVClass dovi_split_class = { + .class_name = "dovi_split_bsf", + .item_name = av_default_item_name, + .option = dovi_split_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +static const enum AVCodecID dovi_split_codec_ids[] = { + AV_CODEC_ID_HEVC, AV_CODEC_ID_NONE, +}; + +const FFBitStreamFilter ff_dovi_split_bsf = { + .p.name = "dovi_split", + .p.codec_ids = dovi_split_codec_ids, + .p.priv_class = &dovi_split_class, + .priv_data_size = sizeof(DOVISplitContext), + .init = dovi_split_init, + .close = dovi_split_close, + .filter = dovi_split_filter, +}; -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
