Commit bf43f4b3cf for ffmpeg

commit bf43f4b3cf443a03e8611338c53e2998fd742bd8
Author: James Almer <jamrial@gmail.com>
Date:   Tue Oct 13 17:35:22 2026 -0300

    avformat/mpegtsenc: add support for muxing LCEVC streams

    Signed-off-by: James Almer <jamrial@gmail.com>

diff --git a/libavformat/lcevc.c b/libavformat/lcevc.c
index 423b90a72b..8328e78b12 100644
--- a/libavformat/lcevc.c
+++ b/libavformat/lcevc.c
@@ -89,6 +89,7 @@ static int write_nalu(LCEVCDecoderConfigurationRecord *lvcc, AVIOContext *pb,

             lvcc->profile_idc = get_bits(&gb, 4);
             lvcc->level_idc = get_bits(&gb, 4);
+            lvcc->sublevel_idc = get_bits(&gb, 2);

             avio_write(pb, raw_gbc.buffer, raw_block_size);
             nalu_length += raw_block_size;
@@ -96,12 +97,11 @@ static int write_nalu(LCEVCDecoderConfigurationRecord *lvcc, AVIOContext *pb,
             break;
         case 1: {
             int resolution_type, bit_depth;
-            int processed_planes_type_flag;

             if (gc)
                 break;

-            processed_planes_type_flag = get_bits1(&gb);
+            lvcc->processed_planes_type_flag = get_bits1(&gb);
             resolution_type = get_bits(&gb, 6);

             skip_bits1(&gb);
@@ -126,7 +126,7 @@ static int write_nalu(LCEVCDecoderConfigurationRecord *lvcc, AVIOContext *pb,
                 skip_bits(&gb, 4);
                 tile_dimensions_type = get_bits(&gb, 2);
                 skip_bits(&gb, 4);
-                if (processed_planes_type_flag)
+                if (lvcc->processed_planes_type_flag)
                     skip_bits(&gb, 4);
                 if (temporal_step_width_modifier_signalled_flag)
                     skip_bits(&gb, 8);
@@ -152,6 +152,19 @@ static int write_nalu(LCEVCDecoderConfigurationRecord *lvcc, AVIOContext *pb,
             gc = 1;
             break;
         }
+        case 2: {
+            int no_enhancement_bit_flag = get_bits1(&gb);
+
+            skip_bits(&gb, 4);
+            lvcc->picture_type_bit_flag = get_bits1(&gb);
+            skip_bits(&gb, 2);
+
+            if (!no_enhancement_bit_flag)
+                skip_bits(&gb, 16);
+
+            lvcc->field_type_bit_flag = lvcc->picture_type_bit_flag ? get_bits1(&gb) : 0;
+            break;
+        }
         case 5:
             avio_write(pb, raw_gbc.buffer, raw_block_size);
             nalu_length += raw_block_size;
diff --git a/libavformat/lcevc.h b/libavformat/lcevc.h
index 4451bcc058..7959779713 100644
--- a/libavformat/lcevc.h
+++ b/libavformat/lcevc.h
@@ -27,11 +27,15 @@
 typedef struct LCEVCDecoderConfigurationRecord {
     uint8_t  profile_idc;
     uint8_t  level_idc;
+    uint8_t  sublevel_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;
+    uint8_t  processed_planes_type_flag;
+    uint8_t  picture_type_bit_flag;
+    uint8_t  field_type_bit_flag;
 } LCEVCDecoderConfigurationRecord;

 int ff_isom_write_lvcc(AVIOContext *pb, const uint8_t *data, int len, void *logctx);
diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c
index 7d04ad03e5..e57889cb2d 100644
--- a/libavformat/mpegtsenc.c
+++ b/libavformat/mpegtsenc.c
@@ -39,6 +39,7 @@
 #include "avformat.h"
 #include "avio_internal.h"
 #include "internal.h"
+#include "lcevc.h"
 #include "mpegts.h"
 #include "mux.h"

@@ -271,6 +272,9 @@ typedef struct MpegTSWriteStream {
     int opus_pending_trim_start;

     DVBAC3Descriptor *dvb_ac3_desc;
+
+    int nb_stream_groups;
+    AVStreamGroup **stream_groups;
 } MpegTSWriteStream;

 static void mpegts_write_pat(AVFormatContext *s)
@@ -379,6 +383,9 @@ static int get_dvb_stream_type(AVFormatContext *s, AVStream *st)
     case AV_CODEC_ID_VVC:
         stream_type = STREAM_TYPE_VIDEO_VVC;
         break;
+    case AV_CODEC_ID_LCEVC:
+        stream_type = STREAM_TYPE_VIDEO_LCEVC;
+        break;
     case AV_CODEC_ID_CAVS:
         stream_type = STREAM_TYPE_VIDEO_CAVS;
         break;
@@ -478,6 +485,9 @@ static int get_m2ts_stream_type(AVFormatContext *s, AVStream *st)
     case AV_CODEC_ID_HEVC:
         stream_type = STREAM_TYPE_VIDEO_HEVC;
         break;
+    case AV_CODEC_ID_LCEVC:
+        stream_type = STREAM_TYPE_VIDEO_LCEVC;
+        break;
     case AV_CODEC_ID_PCM_BLURAY:
         stream_type = STREAM_TYPE_BLURAY_AUDIO_PCM_BLURAY;
         break;
@@ -815,6 +825,31 @@ static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service)
             } else if (stream_type == STREAM_TYPE_VIDEO_CAVS || stream_type == STREAM_TYPE_VIDEO_AVS2 ||
                        stream_type == STREAM_TYPE_VIDEO_AVS3) {
                 put_registration_descriptor(&q, MKTAG('A', 'V', 'S', 'V'));
+            } else if (ts_st->nb_stream_groups) {
+                if (stream_type == STREAM_TYPE_VIDEO_LCEVC) {
+                    LCEVCDecoderConfigurationRecord lvcc = { 0 };
+                    ff_lcvec_parse_config_record(&lvcc, st->codecpar->extradata,
+                                                 st->codecpar->extradata_size, s);
+                    *q++ = EXTENSION_DESCRIPTOR;
+                    *q++ = 5;
+                    *q++ = LCEVC_VIDEO_DESCRIPTOR;
+                    *q++ = st->id;
+                    *q++ = (lvcc.profile_idc << 4) | lvcc.level_idc;
+                    *q++ = (lvcc.sublevel_idc << 6) |
+                           (lvcc.processed_planes_type_flag << 5) |
+                           (lvcc.picture_type_bit_flag << 4) |
+                           (lvcc.field_type_bit_flag << 3);
+                    *q++ = 0xC0; // HDR_WCG_idc == 3, video_properties_tag == 0
+                } else {
+                    *q++ = EXTENSION_DESCRIPTOR;
+                    *q++ = 2 + ts_st->nb_stream_groups;
+                    *q++ = LCEVC_LINKAGE_DESCRIPTOR;
+                    *q++ = ts_st->nb_stream_groups;
+                    for (int j = 0; j < ts_st->nb_stream_groups; j++) {
+                        AVStreamGroup *stg = ts_st->stream_groups[j];
+                        *q++ = stg->entries[stg->params.layered_video->el_index]->stream->id;
+                    }
+                }
             }
             break;
         case AVMEDIA_TYPE_DATA:
@@ -1296,6 +1331,35 @@ static int mpegts_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;
+
+        if (stg->nb_entries != 2) {
+            av_log(s, AV_LOG_ERROR, "LCEVC Stream Group #%"PRId64" needs exactly two streams\n", stg->id);
+            return AVERROR(EINVAL);
+        }
+
+        for (j = 0; j < stg->nb_entries; j++) {
+            AVStreamGroupEntry *entry = stg->entries[j];
+            AVStream *st;
+            MpegTSWriteStream *ts_st;
+
+            if (entry->type != AV_STREAM_GROUP_ENTRY_STREAM) {
+                av_log(s, AV_LOG_ERROR, "LCEVC Stream Group #%"PRId64" must only reference streams\n", stg->id);
+                return AVERROR(EINVAL);
+            }
+            st = entry->stream;
+            ts_st = st->priv_data;
+
+            av_dynarray_add((void *)&ts_st->stream_groups, &ts_st->nb_stream_groups, (void *)stg);
+            if (!ts_st->nb_stream_groups)
+                return AVERROR(ENOMEM);
+        }
+    }
+
     if (ts->copyts < 1)
         ts->first_pcr = av_rescale(s->max_delay, PCR_TIME_BASE, AV_TIME_BASE);

@@ -2349,6 +2413,7 @@ static void mpegts_deinit(AVFormatContext *s)
         if (ts_st) {
             av_freep(&ts_st->dvb_ac3_desc);
             av_freep(&ts_st->payload);
+            av_freep(&ts_st->stream_groups);
             if (ts_st->amux) {
                 avformat_free_context(ts_st->amux);
                 ts_st->amux = NULL;