This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit 0878ae59f900f74f32b1bc86fd425c0d64dae539 Author: James Almer <[email protected]> AuthorDate: Mon Feb 16 12:02:14 2026 -0300 Commit: James Almer <[email protected]> CommitDate: Sat Mar 14 20:50:21 2026 -0300 avformat/movenc: add support for LCEVC track muxing Signed-off-by: James Almer <[email protected]> --- Changelog | 1 + libavcodec/Makefile | 1 + libavformat/Makefile | 4 +- .../half2float.c => libavformat/h2645_parse.c | 2 +- libavformat/lcevc.c | 278 +++++++++++++++++++++ libavutil/reverse.h => libavformat/lcevc.h | 11 +- libavfilter/fflcms2.c => libavformat/lcevctab.c | 2 +- libavformat/movenc.c | 81 +++++- libavformat/version.h | 2 +- 9 files changed, 371 insertions(+), 11 deletions(-) diff --git a/Changelog b/Changelog index 4d2a76aec7..a4241958bc 100644 --- a/Changelog +++ b/Changelog @@ -3,6 +3,7 @@ releases are sorted from youngest to oldest. version <next>: - Extend AMF Color Converter (vf_vpp_amf) HDR capabilities +- LCEVC track muxing support in MP4 muxer version 8.1: diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 1f6c20a09b..9f7ef689c5 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -1135,6 +1135,7 @@ STLIBOBJS-$(CONFIG_IMAGE_JPEGXL_PIPE_DEMUXER) += jpegxl_parse.o STLIBOBJS-$(CONFIG_JPEGXL_ANIM_DEMUXER) += jpegxl_parse.o STLIBOBJS-$(CONFIG_MATROSKA_DEMUXER) += mpeg4audio_sample_rates.o STLIBOBJS-$(CONFIG_MOV_DEMUXER) += ac3_channel_layout_tab.o +STLIBOBJS-$(CONFIG_MOV_MUXER) += h2645_parse.o lcevctab.o STLIBOBJS-$(CONFIG_MXF_MUXER) += golomb.o STLIBOBJS-$(CONFIG_MP3_MUXER) += mpegaudiotabs.o STLIBOBJS-$(CONFIG_NUT_MUXER) += mpegaudiotabs.o diff --git a/libavformat/Makefile b/libavformat/Makefile index 4786a9345a..2fdfd61c9b 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -389,7 +389,8 @@ OBJS-$(CONFIG_MOV_DEMUXER) += mov.o mov_chan.o mov_esds.o \ OBJS-$(CONFIG_MOV_MUXER) += movenc.o \ movenchint.o mov_chan.o rtp.o \ movenccenc.o movenc_ttml.o rawutils.o \ - apv.o dovi_isom.o evc.o cbs.o cbs_av1.o cbs_apv.o + apv.o dovi_isom.o evc.o cbs.o cbs_av1.o cbs_apv.o \ + lcevc.o OBJS-$(CONFIG_MP2_MUXER) += rawenc.o OBJS-$(CONFIG_MP3_DEMUXER) += mp3dec.o replaygain.o OBJS-$(CONFIG_MP3_MUXER) += mp3enc.o rawenc.o id3v2enc.o @@ -751,6 +752,7 @@ SHLIBOBJS-$(CONFIG_JPEGXL_ANIM_DEMUXER) += jpegxl_parse.o SHLIBOBJS-$(CONFIG_MATROSKA_DEMUXER) += mpeg4audio_sample_rates.o SHLIBOBJS-$(CONFIG_MATROSKA_MUXER) += opus_frame_duration_tab.o SHLIBOBJS-$(CONFIG_MOV_DEMUXER) += ac3_channel_layout_tab.o +SHLIBOBJS-$(CONFIG_MOV_MUXER) += h2645_parse.o lcevctab.o SHLIBOBJS-$(CONFIG_MP3_MUXER) += mpegaudiotabs.o SHLIBOBJS-$(CONFIG_MXF_MUXER) += golomb_tab.o \ rangecoder_dec.o diff --git a/libavcodec/half2float.c b/libavformat/h2645_parse.c similarity index 95% copy from libavcodec/half2float.c copy to libavformat/h2645_parse.c index 1b023f96a5..5a6ce628e1 100644 --- a/libavcodec/half2float.c +++ b/libavformat/h2645_parse.c @@ -16,4 +16,4 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "libavutil/half2float.c" +#include "libavcodec/h2645_parse.c" diff --git a/libavformat/lcevc.c b/libavformat/lcevc.c new file mode 100644 index 0000000000..1cadefce11 --- /dev/null +++ b/libavformat/lcevc.c @@ -0,0 +1,278 @@ +/* + * LCEVC helper functions for muxers + * + * 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/error.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/mem.h" +#include "libavcodec/bytestream.h" +#include "libavcodec/h2645_parse.h" +#include "libavcodec/lcevc.h" +#include "libavcodec/lcevctab.h" +#include "libavcodec/lcevc_parse.h" +#include "avio.h" +#include "avio_internal.h" +#include "lcevc.h" + +typedef struct LCEVCDecoderConfigurationRecord { + uint8_t profile_idc; + uint8_t level_idc; + uint8_t chroma_format_idc; + uint8_t bit_depth_luma_minus8; + uint8_t bit_depth_chroma_minus8; + uint32_t pic_width_in_luma_samples; + uint32_t pic_height_in_luma_samples; +} LCEVCDecoderConfigurationRecord; + +/** + * Rewrite the NALu stripping the unneeded blocks. + * Given that length fields coded inside the NALu are not aware of any emulation_3bytes + * present in the bitstream, we need to keep track of the raw buffer as we navigate + * the stripped buffer in order to write proper NALu sizes. + */ +static int write_nalu(LCEVCDecoderConfigurationRecord *lvcc, AVIOContext *pb, + const H2645NAL *nal) +{ + GetByteContext gbc, raw_gbc; + int64_t start = avio_tell(pb), end; + int sc = 0, gc = 0; + int skipped_byte_pos = 0, nalu_length = 3; + + bytestream2_init(&gbc, nal->data, nal->size); + bytestream2_init(&raw_gbc, nal->raw_data, nal->raw_size); + avio_wb16(pb, 0); // size placeholder + avio_wb16(pb, bytestream2_get_be16(&gbc)); // nal_unit_header + bytestream2_skip(&raw_gbc, 2); + + while (bytestream2_get_bytes_left(&gbc) > 1 && (!sc || !gc)) { + GetBitContext gb; + uint64_t payload_size; + int payload_size_type, payload_type; + int block_size, raw_block_size, block_end; + + init_get_bits8(&gb, gbc.buffer, bytestream2_get_bytes_left(&gbc)); + + payload_size_type = get_bits(&gb, 3); + payload_type = get_bits(&gb, 5); + payload_size = payload_size_type; + if (payload_size_type == 6) + return AVERROR_PATCHWELCOME; + if (payload_size_type == 7) + payload_size = get_mb(&gb); + + if (payload_size > INT_MAX - (get_bits_count(&gb) >> 3)) + return AVERROR_INVALIDDATA; + + block_size = raw_block_size = payload_size + (get_bits_count(&gb) >> 3); + if (block_size >= bytestream2_get_bytes_left(&gbc)) + return AVERROR_INVALIDDATA; + + block_end = bytestream2_tell(&gbc) + block_size; + // Take into account removed emulation 3bytes, as payload_size in + // the bitstream is not aware of them. + for (; skipped_byte_pos < nal->skipped_bytes; skipped_byte_pos++) { + if (nal->skipped_bytes_pos[skipped_byte_pos] >= block_end) + break; + raw_block_size++; + } + + switch (payload_type) { + case 0: + if (sc) + break; + + lvcc->profile_idc = get_bits(&gb, 4); + lvcc->level_idc = get_bits(&gb, 4); + + avio_write(pb, raw_gbc.buffer, raw_block_size); + nalu_length += raw_block_size; + sc = 1; + break; + case 1: { + int resolution_type, bit_depth; + int processed_planes_type_flag; + + if (gc) + break; + + processed_planes_type_flag = get_bits1(&gb); + resolution_type = get_bits(&gb, 6); + + skip_bits1(&gb); + lvcc->chroma_format_idc = get_bits(&gb, 2); + + skip_bits(&gb, 2); + bit_depth = get_bits(&gb, 2) * 2; // enhancement_depth_type + lvcc->bit_depth_luma_minus8 = bit_depth; + lvcc->bit_depth_chroma_minus8 = bit_depth; + + if (resolution_type < 63) { + lvcc->pic_width_in_luma_samples = ff_lcevc_resolution_type[resolution_type].width; + lvcc->pic_height_in_luma_samples = ff_lcevc_resolution_type[resolution_type].height; + } else { + int upsample_type, tile_dimensions_type; + int temporal_step_width_modifier_signalled_flag, level1_filtering_signalled_flag; + // Skip syntax elements until we get to the custom dimension ones + temporal_step_width_modifier_signalled_flag = get_bits1(&gb); + skip_bits(&gb, 3); + upsample_type = get_bits(&gb, 3); + level1_filtering_signalled_flag = get_bits1(&gb); + skip_bits(&gb, 4); + tile_dimensions_type = get_bits(&gb, 2); + skip_bits(&gb, 4); + if (processed_planes_type_flag) + skip_bits(&gb, 4); + if (temporal_step_width_modifier_signalled_flag) + skip_bits(&gb, 8); + if (upsample_type) + skip_bits_long(&gb, 64); + if (level1_filtering_signalled_flag) + skip_bits(&gb, 8); + if (tile_dimensions_type) { + if (tile_dimensions_type == 3) + skip_bits_long(&gb, 32); + skip_bits(&gb, 8); + } + + lvcc->pic_width_in_luma_samples = get_bits(&gb, 16); + lvcc->pic_height_in_luma_samples = get_bits(&gb, 16); + } + + if (!lvcc->pic_width_in_luma_samples || !lvcc->pic_height_in_luma_samples) + break; + + avio_write(pb, raw_gbc.buffer, raw_block_size); + nalu_length += raw_block_size; + gc = 1; + break; + } + case 5: + avio_write(pb, raw_gbc.buffer, raw_block_size); + nalu_length += raw_block_size; + break; + default: + break; + } + + bytestream2_skip(&gbc, block_size); + bytestream2_skip(&raw_gbc, raw_block_size); + } + + if (!sc || !gc) + return AVERROR_INVALIDDATA; + + avio_w8(pb, 0x80); // rbsp_alignment bits + + end = avio_tell(pb); + avio_seek(pb, start, SEEK_SET); + avio_wb16(pb, nalu_length); + avio_seek(pb, end, SEEK_SET); + + return 0; +} + +int ff_isom_write_lvcc(AVIOContext *pb, const uint8_t *data, int len) +{ + LCEVCDecoderConfigurationRecord lvcc = { 0 }; + AVIOContext *idr_pb = NULL, *nidr_pb = NULL; + H2645Packet h2645_pkt = { 0 }; + uint8_t *idr, *nidr; + uint32_t idr_size = 0, nidr_size = 0; + int ret, nb_idr = 0, nb_nidr = 0; + + if (len <= 6) + return AVERROR_INVALIDDATA; + + /* check for start code */ + if (AV_RB32(data) != 0x00000001 && + AV_RB24(data) != 0x000001) { + avio_write(pb, data, len); + return 0; + } + + ret = ff_h2645_packet_split(&h2645_pkt, data, len, NULL, 0, AV_CODEC_ID_LCEVC, 0); + if (ret < 0) + return ret; + + ret = avio_open_dyn_buf(&idr_pb); + if (ret < 0) + goto fail; + ret = avio_open_dyn_buf(&nidr_pb); + if (ret < 0) + goto fail; + + /* look for IDR or NON_IDR */ + for (int i = 0; i < h2645_pkt.nb_nals; i++) { + const H2645NAL *nal = &h2645_pkt.nals[i]; + + if (nal->type == LCEVC_IDR_NUT) { + nb_idr++; + + ret = write_nalu(&lvcc, idr_pb, nal); + if (ret < 0) + return ret; + } else if (nal->type == LCEVC_NON_IDR_NUT) { + nb_nidr++; + + ret = write_nalu(&lvcc, nidr_pb, nal); + if (ret < 0) + return ret; + } + } + idr_size = avio_get_dyn_buf(idr_pb, &idr); + nidr_size = avio_get_dyn_buf(nidr_pb, &nidr); + + if (!idr_size && !nidr_size) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + + avio_w8(pb, 1); /* version */ + avio_w8(pb, lvcc.profile_idc); + avio_w8(pb, lvcc.level_idc); + avio_w8(pb, (lvcc.chroma_format_idc << 6) | + (lvcc.bit_depth_luma_minus8 << 3) | + lvcc.bit_depth_chroma_minus8); + avio_w8(pb, 0xff); /* 2 bits nal size length - 1 (11) + 6 bits reserved (111111)*/ + avio_wb32(pb, lvcc.pic_width_in_luma_samples); + avio_wb32(pb, lvcc.pic_height_in_luma_samples); + avio_w8(pb, 0xff); + + int nb_arrays = !!nb_idr + !!nb_nidr; + avio_w8(pb, nb_arrays); + + if (nb_idr) { + avio_w8(pb, LCEVC_IDR_NUT); + avio_wb16(pb, nb_idr); + avio_write(pb, idr, idr_size); + } + if (nb_nidr) { + avio_w8(pb, LCEVC_NON_IDR_NUT); + avio_wb16(pb, nb_idr); + avio_write(pb, nidr, nidr_size); + } + + ret = 0; +fail: + ffio_free_dyn_buf(&idr_pb); + ffio_free_dyn_buf(&nidr_pb); + ff_h2645_packet_uninit(&h2645_pkt); + + return ret; +} diff --git a/libavutil/reverse.h b/libavformat/lcevc.h similarity index 79% copy from libavutil/reverse.h copy to libavformat/lcevc.h index 4eb6123932..76093c4a36 100644 --- a/libavutil/reverse.h +++ b/libavformat/lcevc.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002-2004 Michael Niedermayer <[email protected]> + * LCEVC helper functions for muxers * * This file is part of FFmpeg. * @@ -18,11 +18,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef AVUTIL_REVERSE_H -#define AVUTIL_REVERSE_H +#ifndef AVFORMAT_LCEVC_H +#define AVFORMAT_LCEVC_H #include <stdint.h> +#include "avio.h" -extern const uint8_t ff_reverse[256]; +int ff_isom_write_lvcc(AVIOContext *pb, const uint8_t *data, int len); -#endif /* AVUTIL_REVERSE_H */ +#endif /* AVFORMAT_LCEVC_H */ diff --git a/libavfilter/fflcms2.c b/libavformat/lcevctab.c similarity index 95% copy from libavfilter/fflcms2.c copy to libavformat/lcevctab.c index 822462de87..3711ec5870 100644 --- a/libavfilter/fflcms2.c +++ b/libavformat/lcevctab.c @@ -16,4 +16,4 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "libavcodec/fflcms2.c" +#include "libavcodec/lcevctab.c" diff --git a/libavformat/movenc.c b/libavformat/movenc.c index fe6b259561..ae2d5ab4f9 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -38,6 +38,7 @@ #include "avc.h" #include "evc.h" #include "apv.h" +#include "lcevc.h" #include "libavcodec/ac3_parser_internal.h" #include "libavcodec/dnxhddata.h" #include "libavcodec/flac.h" @@ -1681,6 +1682,19 @@ static int mov_write_evcc_tag(AVIOContext *pb, MOVTrack *track) return update_size(pb, pos); } +static int mov_write_lvcc_tag(AVIOContext *pb, MOVTrack *track) +{ + int64_t pos = avio_tell(pb); + + avio_wb32(pb, 0); + ffio_wfourcc(pb, "lvcC"); + + ff_isom_write_lvcc(pb, track->extradata[track->last_stsd_index], + track->extradata_size[track->last_stsd_index]); + + return update_size(pb, pos); +} + static int mov_write_vvcc_tag(AVIOContext *pb, MOVTrack *track) { int64_t pos = avio_tell(pb); @@ -2880,6 +2894,8 @@ static int mov_write_video_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex } else if (track->par->codec_id ==AV_CODEC_ID_EVC) { mov_write_evcc_tag(pb, track); + } else if (track->par->codec_id == AV_CODEC_ID_LCEVC) { + mov_write_lvcc_tag(pb, track); } else if (track->par->codec_id ==AV_CODEC_ID_APV) { mov_write_apvc_tag(mov->fc, pb, track); } else if (track->par->codec_id == AV_CODEC_ID_VP9) { @@ -5222,6 +5238,9 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov, if (track->tag == MKTAG('r','t','p',' ')) { track->tref_tag = MKTAG('h','i','n','t'); track->tref_id = mov->tracks[track->src_track].track_id; + } else if (track->tag == MKTAG('l','v','c','1')) { + track->tref_tag = MKTAG('s','b','a','s'); + track->tref_id = mov->tracks[track->src_track].track_id; } else if (track->par->codec_type == AVMEDIA_TYPE_AUDIO) { const AVPacketSideData *sd = av_packet_side_data_get(track->st->codecpar->coded_side_data, track->st->codecpar->nb_coded_side_data, @@ -6847,6 +6866,7 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) par->codec_id == AV_CODEC_ID_VVC || par->codec_id == AV_CODEC_ID_VP9 || par->codec_id == AV_CODEC_ID_EVC || + par->codec_id == AV_CODEC_ID_LCEVC || par->codec_id == AV_CODEC_ID_TRUEHD) && !trk->extradata_size[0] && !TAG_IS_AVCI(trk->tag)) { /* copy frame to create needed atoms */ @@ -6957,6 +6977,25 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) } else { size = ff_vvc_annexb2mp4(pb, pkt->data, pkt->size, 0, NULL); } + } else if (par->codec_id == AV_CODEC_ID_LCEVC && trk->extradata_size[trk->last_stsd_index] > 0 && + *(uint8_t *)trk->extradata[trk->last_stsd_index] != 1) { + /* extradata is Annex B, assume the bitstream is too and convert it */ + if (trk->hint_track >= 0 && trk->hint_track < mov->nb_tracks) { + ret = ff_nal_parse_units_buf(pkt->data, &reformatted_data, &size); + if (ret < 0) + return ret; + avio_write(pb, reformatted_data, size); + } else { + if (trk->cenc.aes_ctr) { + size = ff_mov_cenc_avc_parse_nal_units(&trk->cenc, pb, pkt->data, size); + if (size < 0) { + ret = size; + goto err; + } + } else { + size = ff_nal_parse_units(pb, pkt->data, pkt->size); + } + } } else if (par->codec_id == AV_CODEC_ID_AV1 && !trk->cenc.aes_ctr) { if (trk->hint_track >= 0 && trk->hint_track < mov->nb_tracks) { ret = ff_av1_filter_obus_buf(pkt->data, &reformatted_data, @@ -8041,10 +8080,25 @@ static int mov_init(AVFormatContext *s) s->streams[0]->disposition |= AV_DISPOSITION_DEFAULT; } -#if CONFIG_IAMFENC for (i = 0; i < s->nb_stream_groups; i++) { AVStreamGroup *stg = s->stream_groups[i]; + if (stg->type == AV_STREAM_GROUP_PARAMS_LCEVC) { + if (stg->nb_streams != 2) { + av_log(s, AV_LOG_ERROR, "Exactly two Streams are supported for Stream Groups of type LCEVC\n"); + return AVERROR(EINVAL); + } + AVStreamGroupLCEVC *lcevc = stg->params.lcevc; + if (lcevc->lcevc_index > 1) + return AVERROR(EINVAL); + AVStream *st = stg->streams[lcevc->lcevc_index]; + if (st->codecpar->codec_id != AV_CODEC_ID_LCEVC) { + av_log(s, AV_LOG_ERROR, "Stream #%u is not an LCEVC stream\n", lcevc->lcevc_index); + return AVERROR(EINVAL); + } + } + +#if CONFIG_IAMFENC if (stg->type != AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT) continue; @@ -8062,8 +8116,8 @@ static int mov_init(AVFormatContext *s) if (!mov->nb_tracks) // We support one track for the entire IAMF structure mov->nb_tracks++; - } #endif + } for (i = 0; i < s->nb_streams; i++) { AVStream *st = s->streams[i]; @@ -8378,6 +8432,28 @@ static int mov_init(AVFormatContext *s) } } + for (i = 0; i < s->nb_stream_groups; i++) { + AVStreamGroup *stg = s->stream_groups[i]; + + if (stg->type != AV_STREAM_GROUP_PARAMS_LCEVC) + continue; + + AVStreamGroupLCEVC *lcevc = stg->params.lcevc; + AVStream *st = stg->streams[lcevc->lcevc_index]; + MOVTrack *track = st->priv_data; + + for (i = 0; i < mov->nb_tracks; i++) { + MOVTrack *trk = &mov->tracks[i]; + + if (trk->st == stg->streams[!lcevc->lcevc_index]) + break; + } + track->src_track = i; + + track->par->width = lcevc->width; + track->par->height = track->height = lcevc->height; + } + enable_tracks(s); return 0; } @@ -8892,6 +8968,7 @@ static const AVCodecTag codec_mp4_tags[] = { { AV_CODEC_ID_VVC, MKTAG('v', 'v', 'c', '1') }, { AV_CODEC_ID_VVC, MKTAG('v', 'v', 'i', '1') }, { AV_CODEC_ID_EVC, MKTAG('e', 'v', 'c', '1') }, + { AV_CODEC_ID_LCEVC, MKTAG('l', 'v', 'c', '1') }, { AV_CODEC_ID_APV, MKTAG('a', 'p', 'v', '1') }, { AV_CODEC_ID_MPEG2VIDEO, MKTAG('m', 'p', '4', 'v') }, { AV_CODEC_ID_MPEG1VIDEO, MKTAG('m', 'p', '4', 'v') }, diff --git a/libavformat/version.h b/libavformat/version.h index e8fad25023..cd62aaf1ca 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -32,7 +32,7 @@ #include "version_major.h" #define LIBAVFORMAT_VERSION_MINOR 13 -#define LIBAVFORMAT_VERSION_MICRO 100 +#define LIBAVFORMAT_VERSION_MICRO 101 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ LIBAVFORMAT_VERSION_MINOR, \ _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
