PR #22470 opened by James Almer (jamrial) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22470 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22470.patch
>From 3c82ebf0a4f0e29a53c723d5966430f225626b6e Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 16 Feb 2026 12:03:04 -0300 Subject: [PATCH 1/4] avformat/options: add missing AVOption for AVStreamGroupLCEVC Signed-off-by: James Almer <[email protected]> --- libavformat/options.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libavformat/options.c b/libavformat/options.c index d08013bfd4..09f104c3a9 100644 --- a/libavformat/options.c +++ b/libavformat/options.c @@ -351,6 +351,8 @@ static const AVClass tile_grid_class = { #define OFFSET(x) offsetof(AVStreamGroupLCEVC, x) static const AVOption lcevc_options[] = { + { "lcevc_index", "size of video after LCEVC enhancement has been applied", OFFSET(lcevc_index), + AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS }, { "video_size", "size of video after LCEVC enhancement has been applied", OFFSET(width), AV_OPT_TYPE_IMAGE_SIZE, { .str = NULL }, 0, INT_MAX, FLAGS }, { NULL }, -- 2.52.0 >From 26f6dcfb0e5a0c4ad5e13e9c6d04780ad405d87e Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 9 Mar 2026 20:20:53 -0300 Subject: [PATCH 2/4] avcodec/lcevc_parser: move the resolution type table to a header Will be useful in the following commit. Signed-off-by: James Almer <[email protected]> --- libavcodec/lcevc_parse.h | 19 +++++++++++++++++++ libavcodec/lcevc_parser.c | 19 ------------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/libavcodec/lcevc_parse.h b/libavcodec/lcevc_parse.h index f56758a1a5..cd9cd212b2 100644 --- a/libavcodec/lcevc_parse.h +++ b/libavcodec/lcevc_parse.h @@ -23,6 +23,25 @@ #include "get_bits.h" +static const struct { + int width; + int height; +} resolution_type_lut[63] = { + { 0, 0}, + { 360, 200 }, { 400, 240 }, { 480, 320 }, { 640, 360 }, + { 640, 480 }, { 768, 480 }, { 800, 600 }, { 852, 480 }, + { 854, 480 }, { 856, 480 }, { 960, 540 }, { 960, 640 }, + { 1024, 576 }, { 1024, 600 }, { 1024, 768 }, { 1152, 864 }, + { 1280, 720 }, { 1280, 800 }, { 1280, 1024 }, { 1360, 768 }, + { 1366, 768 }, { 1920, 1200 }, { 2048, 1080 }, { 2048, 1152 }, + { 2048, 1536 }, { 2160, 1440 }, { 2560, 1440 }, { 2560, 1600 }, + { 2560, 2048 }, { 3200, 1800 }, { 3200, 2048 }, { 3200, 2400 }, + { 3440, 1440 }, { 3840, 1600 }, { 3840, 2160 }, { 3840, 2400 }, + { 4096, 2160 }, { 4096, 3072 }, { 5120, 2880 }, { 5120, 3200 }, + { 5120, 4096 }, { 6400, 4096 }, { 6400, 4800 }, { 7680, 4320 }, + { 7680, 4800 }, +}; + static inline uint64_t get_mb(GetBitContext *s) { int more, i = 0; uint64_t mb = 0; diff --git a/libavcodec/lcevc_parser.c b/libavcodec/lcevc_parser.c index 1de44a8bda..766d98e26e 100644 --- a/libavcodec/lcevc_parser.c +++ b/libavcodec/lcevc_parser.c @@ -82,25 +82,6 @@ static const enum AVPixelFormat pix_fmts[4][4] = { AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14, }, }; -static const struct { - int width; - int height; -} resolution_type_lut[63] = { - { 0, 0}, - { 360, 200 }, { 400, 240 }, { 480, 320 }, { 640, 360 }, - { 640, 480 }, { 768, 480 }, { 800, 600 }, { 852, 480 }, - { 854, 480 }, { 856, 480 }, { 960, 540 }, { 960, 640 }, - { 1024, 576 }, { 1024, 600 }, { 1024, 768 }, { 1152, 864 }, - { 1280, 720 }, { 1280, 800 }, { 1280, 1024 }, { 1360, 768 }, - { 1366, 768 }, { 1920, 1200 }, { 2048, 1080 }, { 2048, 1152 }, - { 2048, 1536 }, { 2160, 1440 }, { 2560, 1440 }, { 2560, 1600 }, - { 2560, 2048 }, { 3200, 1800 }, { 3200, 2048 }, { 3200, 2400 }, - { 3440, 1440 }, { 3840, 1600 }, { 3840, 2160 }, { 3840, 2400 }, - { 4096, 2160 }, { 4096, 3072 }, { 5120, 2880 }, { 5120, 3200 }, - { 5120, 4096 }, { 6400, 4096 }, { 6400, 4800 }, { 7680, 4320 }, - { 7680, 4800 }, -}; - static int parse_nal_unit(AVCodecParserContext *s, AVCodecContext *avctx, const H2645NAL *nal) { -- 2.52.0 >From d9a2f38c5dcc37999f2c4786b05d41393ef4cab9 Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 16 Feb 2026 12:02:14 -0300 Subject: [PATCH 3/4] avformat/movenc: add support for LCEVC track muxing Signed-off-by: James Almer <[email protected]> --- libavcodec/Makefile | 1 + libavformat/Makefile | 4 +- libavformat/h2645_parse.c | 19 +++ libavformat/lcevc.c | 278 ++++++++++++++++++++++++++++++++++++++ libavformat/lcevc.h | 29 ++++ libavformat/movenc.c | 54 +++++++- 6 files changed, 380 insertions(+), 5 deletions(-) create mode 100644 libavformat/h2645_parse.c create mode 100644 libavformat/lcevc.c create mode 100644 libavformat/lcevc.h diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 12a8265025..b7afb6a5ce 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -1129,6 +1129,7 @@ OBJS-$(CONFIG_TAK_DEMUXER) += tak.o # libavformat dependencies for static builds STLIBOBJS-$(CONFIG_AVFORMAT) += to_upper4.o STLIBOBJS-$(CONFIG_ISO_MEDIA) += mpegaudiotabs.o +STLIBOBJS-$(CONFIG_ISO_WRITER) += h2645_parse.o STLIBOBJS-$(CONFIG_FLV_MUXER) += mpeg4audio_sample_rates.o STLIBOBJS-$(CONFIG_HLS_DEMUXER) += ac3_channel_layout_tab.o STLIBOBJS-$(CONFIG_IMAGE_JPEGXL_PIPE_DEMUXER) += jpegxl_parse.o diff --git a/libavformat/Makefile b/libavformat/Makefile index 4786a9345a..4ffefb83cc 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -40,7 +40,7 @@ OBJS-$(HAVE_LIBC_MSVCRT) += file_open.o # subsystems OBJS-$(CONFIG_ISO_MEDIA) += isom.o -OBJS-$(CONFIG_ISO_WRITER) += avc.o hevc.o vvc.o +OBJS-$(CONFIG_ISO_WRITER) += avc.o hevc.o vvc.o lcevc.o OBJS-$(CONFIG_IAMFDEC) += iamf_reader.o iamf_parse.o iamf.o OBJS-$(CONFIG_IAMFENC) += iamf_writer.o iamf.o OBJS-$(CONFIG_NETWORK) += network.o @@ -742,7 +742,7 @@ OBJS-$(CONFIG_LIBZMQ_PROTOCOL) += libzmq.o # Objects duplicated from other libraries for shared builds SHLIBOBJS += log2_tab.o to_upper4.o SHLIBOBJS-$(CONFIG_ISO_MEDIA) += mpegaudiotabs.o -SHLIBOBJS-$(CONFIG_ISO_WRITER) += golomb_tab.o +SHLIBOBJS-$(CONFIG_ISO_WRITER) += golomb_tab.o h2645_parse.o SHLIBOBJS-$(CONFIG_FLV_MUXER) += mpeg4audio_sample_rates.o SHLIBOBJS-$(CONFIG_HLS_DEMUXER) += ac3_channel_layout_tab.o SHLIBOBJS-$(CONFIG_IMAGE_JPEGXL_PIPE_DEMUXER) += jpegxl_parse.o diff --git a/libavformat/h2645_parse.c b/libavformat/h2645_parse.c new file mode 100644 index 0000000000..5a6ce628e1 --- /dev/null +++ b/libavformat/h2645_parse.c @@ -0,0 +1,19 @@ +/* + * 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 "libavcodec/h2645_parse.c" diff --git a/libavformat/lcevc.c b/libavformat/lcevc.c new file mode 100644 index 0000000000..9f2e61e8b7 --- /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/lcevc_parse.h" +#include "libavcodec/leb.h" +#include "avio.h" +#include "avio_internal.h" +#include "lcevc.h" +#include "nal.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 unnedded 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 = resolution_type_lut[resolution_type].width; + lvcc->pic_height_in_luma_samples = resolution_type_lut[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) != 1) { + 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/libavformat/lcevc.h b/libavformat/lcevc.h new file mode 100644 index 0000000000..76093c4a36 --- /dev/null +++ b/libavformat/lcevc.h @@ -0,0 +1,29 @@ +/* + * 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 + */ + +#ifndef AVFORMAT_LCEVC_H +#define AVFORMAT_LCEVC_H + +#include <stdint.h> +#include "avio.h" + +int ff_isom_write_lvcc(AVIOContext *pb, const uint8_t *data, int len); + +#endif /* AVFORMAT_LCEVC_H */ diff --git a/libavformat/movenc.c b/libavformat/movenc.c index fe6b259561..b78a02afef 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 */ @@ -8041,10 +8061,15 @@ 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 && stg->nb_streams != 2) { + av_log(s, AV_LOG_ERROR, "Exactly two Streams are needed for Strem Groups of type LCEVC\n"); + return AVERROR(EINVAL); + } + +#if CONFIG_IAMFENC if (stg->type != AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT) continue; @@ -8062,8 +8087,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 +8403,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; } @@ -8530,7 +8577,7 @@ static int mov_write_header(AVFormatContext *s) AVStream *st = mov->tracks[i].st; t = global_tcr; - if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + if (mov->tracks[i].par->codec_type == AVMEDIA_TYPE_VIDEO) { AVTimecode tc; if (!t) t = av_dict_get(st->metadata, "timecode", NULL, 0); @@ -8892,6 +8939,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') }, -- 2.52.0 >From 229ee9dec1fb88fc843f76b6964a329bf88a1476 Mon Sep 17 00:00:00 2001 From: James Almer <[email protected]> Date: Mon, 16 Feb 2026 12:03:28 -0300 Subject: [PATCH 4/4] fftools/ffmpeg_mux_init: add support for LCEVC Stream Group muxing Signed-off-by: James Almer <[email protected]> --- fftools/ffmpeg_mux_init.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fftools/ffmpeg_mux_init.c b/fftools/ffmpeg_mux_init.c index 7e51ce76ad..e0eecf78f2 100644 --- a/fftools/ffmpeg_mux_init.c +++ b/fftools/ffmpeg_mux_init.c @@ -2552,6 +2552,8 @@ static int of_map_group(Muxer *mux, AVDictionary **dict, AVBPrint *bp, const cha } break; } + case AV_STREAM_GROUP_PARAMS_LCEVC: + break; default: av_log(mux, AV_LOG_ERROR, "Unsupported mapped group type %d.\n", stg->type); ret = AVERROR(EINVAL); @@ -2574,6 +2576,8 @@ static int of_parse_group_token(Muxer *mux, const char *token, char *ptr) { .i64 = AV_STREAM_GROUP_PARAMS_IAMF_AUDIO_ELEMENT }, .unit = "type" }, { "iamf_mix_presentation", NULL, 0, AV_OPT_TYPE_CONST, { .i64 = AV_STREAM_GROUP_PARAMS_IAMF_MIX_PRESENTATION }, .unit = "type" }, + { "lcevc", NULL, 0, AV_OPT_TYPE_CONST, + { .i64 = AV_STREAM_GROUP_PARAMS_LCEVC }, .unit = "type" }, { NULL }, }; const AVClass class = { @@ -2672,8 +2676,6 @@ static int of_parse_group_token(Muxer *mux, const char *token, char *ptr) ret = of_parse_iamf_submixes(mux, stg, ptr); break; default: - av_log(mux, AV_LOG_FATAL, "Unknown group type %d.\n", type); - ret = AVERROR(EINVAL); break; } -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
