This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit 6026988b753ebb1bd424612f40b17c0c363d8ed7 Author: Kacper Michajłow <[email protected]> AuthorDate: Sat May 16 18:40:32 2026 +0200 Commit: Kacper Michajłow <[email protected]> CommitDate: Sun May 31 16:58:23 2026 +0200 avcodec/bsf: add dovi_split BSF Allows splitting interleaved BL+EL HEVC bitstream into separate streams. Signed-off-by: Kacper Michajłow <[email protected]> --- Changelog | 1 + configure | 1 + doc/bitstream_filters.texi | 27 ++++ libavcodec/bitstream_filters.c | 1 + libavcodec/bsf/Makefile | 1 + libavcodec/bsf/dovi_split.c | 278 +++++++++++++++++++++++++++++++++++++++++ libavcodec/version.h | 2 +- 7 files changed, 310 insertions(+), 1 deletion(-) diff --git a/Changelog b/Changelog index aff0c78153..3c48005026 100644 --- a/Changelog +++ b/Changelog @@ -16,6 +16,7 @@ version <next>: - Animated WebP demuxer - Remove CELT decoding support (doesn't affect Opus CELT) - Remove ogg/celt parsing +- Bitstream filter to split Dolby Vision multi-layer HEVC version 8.1: diff --git a/configure b/configure index 1abf53b678..b0923e4789 100755 --- a/configure +++ b/configure @@ -3759,6 +3759,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..c6705bbd39 100644 --- a/doc/bitstream_filters.texi +++ b/doc/bitstream_filters.texi @@ -124,6 +124,33 @@ 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). + +@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..3d9531627a --- /dev/null +++ b/libavcodec/bsf/dovi_split.c @@ -0,0 +1,278 @@ +/* + * 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 <stdbool.h> + +#include "libavutil/avassert.h" +#include "libavutil/dovi_meta.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 "packet.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 */ + int out_nal_length_size; /* 0 means Annex-B output */ + H2645Packet pkt; +} DOVISplitContext; + +static int hvcc_nal_length_size(const uint8_t *data, int size) +{ + if (size >= 23 && data[0] == 1 && AV_RB24(data) != 1 && AV_RB32(data) != 1) + return (data[21] & 3) + 1; + return 0; +} + +static int dovi_split_init(AVBSFContext *ctx) +{ + DOVISplitContext *s = ctx->priv_data; + bool keep_bl = s->mode == DOVI_SPLIT_BL || s->mode == DOVI_SPLIT_BL_RPU; + bool keep_el = s->mode == DOVI_SPLIT_EL || s->mode == DOVI_SPLIT_EL_RPU; + bool keep_rpu = s->mode == DOVI_SPLIT_BL_RPU || s->mode == DOVI_SPLIT_EL_RPU; + + /* Profile 7 is currently the only supported variant with EL by Dolby. + * Default to that in case there is no DOVI config. */ + uint8_t dv_profile = 7; + + for (int i = 0; i < ctx->par_out->nb_coded_side_data; i++) { + AVPacketSideData *sd = &ctx->par_out->coded_side_data[i]; + AVDOVIDecoderConfigurationRecord *cfg; + if (sd->type != AV_PKT_DATA_DOVI_CONF) + continue; + cfg = (AVDOVIDecoderConfigurationRecord *)sd->data; + cfg->bl_present_flag &= keep_bl; + cfg->el_present_flag &= keep_el; + cfg->rpu_present_flag &= keep_rpu; + dv_profile = cfg->dv_profile; + break; + } + + if (keep_el) { + const AVPacketSideData *sd; + sd = av_packet_side_data_get(ctx->par_out->coded_side_data, + ctx->par_out->nb_coded_side_data, + AV_PKT_DATA_HEVC_CONF); + if (sd && sd->size >= 23) { + uint8_t *new_ed = av_mallocz(sd->size + AV_INPUT_BUFFER_PADDING_SIZE); + if (!new_ed) + return AVERROR(ENOMEM); + memcpy(new_ed, sd->data, sd->size); + av_freep(&ctx->par_out->extradata); + ctx->par_out->extradata = new_ed; + ctx->par_out->extradata_size = sd->size; + } + + /* DV profile to EL size ratio */ + static const uint8_t el_div[] = { + [2] = 2, + [3] = 1, + [4] = 2, + [6] = 2, + [7] = 2, + }; + int div = dv_profile < FF_ARRAY_ELEMS(el_div) ? el_div[dv_profile] : 0; + if (!div) + av_log(ctx, AV_LOG_WARNING, "Unexpected DV Profile %d.\n", dv_profile); + + /* P7: EL is 1:1 for FHD BL */ + if (dv_profile == 7 && ctx->par_in->width <= 1920) + div = 1; + if (div > 1) { + ctx->par_out->width = ctx->par_in->width / div; + ctx->par_out->height = ctx->par_in->height / div; + } + } + + /* Drop AV_PKT_DATA_HEVC_CONF as it's no longer valid on output. It's + * set as extradata for EL. */ + av_packet_side_data_remove(ctx->par_out->coded_side_data, + &ctx->par_out->nb_coded_side_data, + AV_PKT_DATA_HEVC_CONF); + + s->nal_length_size = hvcc_nal_length_size(ctx->par_in->extradata, + ctx->par_in->extradata_size); + s->out_nal_length_size = hvcc_nal_length_size(ctx->par_out->extradata, + ctx->par_out->extradata_size); + + 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) +{ + bool keep_el = s->mode == DOVI_SPLIT_EL || s->mode == DOVI_SPLIT_EL_RPU; + bool keep_bl = s->mode == DOVI_SPLIT_BL || s->mode == DOVI_SPLIT_BL_RPU; + bool 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 prefix_size = s->out_nal_length_size ? s->out_nal_length_size : 4; + 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; + out_size += prefix_size + 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; + switch (s->out_nal_length_size) { + case 0: AV_WB32(dst, 1); break; + case 1: AV_WB8 (dst, payload_size); break; + case 2: AV_WB16(dst, payload_size); break; + case 3: AV_WB24(dst, payload_size); break; + case 4: AV_WB32(dst, payload_size); break; + } + dst += prefix_size; + 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, +}; diff --git a/libavcodec/version.h b/libavcodec/version.h index 1008fead27..34b059a8a9 100644 --- a/libavcodec/version.h +++ b/libavcodec/version.h @@ -29,7 +29,7 @@ #include "version_major.h" -#define LIBAVCODEC_VERSION_MINOR 35 +#define LIBAVCODEC_VERSION_MINOR 36 #define LIBAVCODEC_VERSION_MICRO 100 #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \ _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
