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;