avformat/mpegts: create Dolby Vision stream group

See: https://professionalsupport.dolby.com/s/article/How-to-signal-Dolby-Vision-in-MPEG-2-TS

Signed-off-by: Kacper Michajłow <kasper93@gmail.com>
This commit is contained in:
Kacper Michajłow
2026-05-31 03:43:29 +00:00
committed by James Almer
co-authored by James Almer
parent 8d89d7c25a
commit 29bc8ec8d1
2 changed files with 122 additions and 6 deletions
+121 -6
View File
@@ -120,6 +120,7 @@ struct Stream {
struct StreamGroup {
enum AVStreamGroupParamsType type;
int id;
int dep_pid; /* PID of the linked dependency stream */
unsigned int nb_streams;
AVStream *streams[MAX_STREAMS_PER_PROGRAM];
};
@@ -2446,6 +2447,36 @@ int ff_parse_mpeg2_descriptor(AVFormatContext *fc, AVStream *st, int stream_type
dependency_pid,
dovi->dv_bl_signal_compatibility_id,
dovi->dv_md_compression);
/* A Profile 7 dual-track EL stream points at its base layer via
* the descriptor's dependency_pid. Record a pending group entry,
* it will be resolved once all streams are available. */
if (dovi->dv_profile == 7 && dovi->el_present_flag &&
!dovi->bl_present_flag && dependency_pid >= 0) {
struct Program *p = get_program(ts, prg_id);
struct StreamGroup *stg;
int gi;
if (!p)
break;
for (gi = 0; gi < p->nb_stream_groups; gi++) {
stg = &p->stream_groups[gi];
if (stg->type == AV_STREAM_GROUP_PARAMS_DOLBY_VISION &&
stg->nb_streams && stg->streams[0] == st)
break;
}
if (gi == p->nb_stream_groups) {
if (p->nb_stream_groups == MAX_STREAMS_PER_PROGRAM)
return AVERROR(EINVAL);
p->nb_stream_groups++;
stg = &p->stream_groups[gi];
stg->type = AV_STREAM_GROUP_PARAMS_DOLBY_VISION;
stg->id = st->id;
stg->streams[stg->nb_streams++] = st;
}
stg->dep_pid = dependency_pid;
}
}
break;
case EXTENSION_DESCRIPTOR: /* descriptor extension */
@@ -2541,12 +2572,81 @@ static int is_pes_stream(int stream_type, uint32_t prog_reg_desc)
}
}
static void create_stream_groups(MpegTSContext *ts, const struct Program *prg)
/* UHD Blu-ray titles don't follow the Dolby Vision MPEG-TS spec [1] when
* signaling a dual-PID Profile 7 stream. The enhancement-layer PID lacks the
* DOVI_video_stream_descriptor, is advertised with stream_type 0x24 (HEVC)
* instead of the spec-mandated 0x06, and uses the HDMV registration descriptor
* instead of "DOVI". EL always has M2TS_VIDEO_EL_PID (0x1015) PID.
* [1] <https://professionalsupport.dolby.com/s/article/How-to-signal-Dolby-Vision-in-MPEG-2-TS>
*/
static void detect_bdmv_dovi_group(MpegTSContext *ts, struct Program *prg,
uint32_t prog_reg_desc)
{
AVStream *bl_st = NULL, *el_st = NULL;
struct StreamGroup *grp;
if (prog_reg_desc != AV_RL32("HDMV"))
return;
if (prg->nb_stream_groups >= MAX_STREAMS_PER_PROGRAM)
return;
for (int i = 0; i < prg->nb_stream_groups; i++) {
if (prg->stream_groups[i].type == AV_STREAM_GROUP_PARAMS_DOLBY_VISION)
return;
}
for (int j = 0; j < prg->nb_streams; j++) {
int idx = prg->streams[j].idx;
AVStream *st;
if (idx < 0 || idx >= ts->stream->nb_streams)
continue;
st = ts->stream->streams[idx];
if (st->codecpar->codec_id != AV_CODEC_ID_HEVC)
continue;
if (st->id == M2TS_VIDEO_PID)
bl_st = st;
else if (st->id == M2TS_VIDEO_EL_PID)
el_st = st;
}
if (!bl_st || !el_st)
return;
grp = &prg->stream_groups[prg->nb_stream_groups++];
grp->type = AV_STREAM_GROUP_PARAMS_DOLBY_VISION;
grp->id = el_st->id;
grp->streams[0] = bl_st;
grp->streams[1] = el_st;
grp->nb_streams = 2;
}
static void create_stream_groups(MpegTSContext *ts, struct Program *prg)
{
for (int i = 0; i < prg->nb_stream_groups; i++) {
const struct StreamGroup *grp = &prg->stream_groups[i];
struct StreamGroup *grp = &prg->stream_groups[i];
AVStreamGroup *stg;
int j;
/* The DOVI descriptor on a Profile 7 EL points at its base layer
* via dependency_pid. Resolve the BL now that all streams of the
* program have been added. */
if (grp->type == AV_STREAM_GROUP_PARAMS_DOLBY_VISION && grp->nb_streams == 1) {
for (j = 0; j < prg->nb_streams; j++) {
int idx = prg->streams[j].idx;
AVStream *cand;
if (idx < 0 || idx >= ts->stream->nb_streams)
continue;
cand = ts->stream->streams[idx];
if (cand == grp->streams[0] || cand->id != grp->dep_pid)
continue;
grp->streams[1] = grp->streams[0];
grp->streams[0] = cand;
grp->nb_streams = 2;
break;
}
}
if (grp->nb_streams < 2)
continue;
for (j = 0; j < ts->stream->nb_stream_groups; j++) {
@@ -2560,7 +2660,6 @@ static void create_stream_groups(MpegTSContext *ts, const struct Program *prg)
continue;
if (!stg)
continue;
av_assert0(grp->type == AV_STREAM_GROUP_PARAMS_LCEVC);
stg->id = grp->id;
for (int j = 0; j < grp->nb_streams; j++) {
int ret = avformat_stream_group_add_stream(stg, grp->streams[j]);
@@ -2568,8 +2667,22 @@ static void create_stream_groups(MpegTSContext *ts, const struct Program *prg)
ff_remove_stream_group(ts->stream, stg);
continue;
}
if (grp->streams[j]->codecpar->codec_id == AV_CODEC_ID_LCEVC)
stg->params.lcevc->lcevc_index = stg->nb_streams - 1;
switch (grp->type) {
case AV_STREAM_GROUP_PARAMS_LCEVC:
if (grp->streams[j]->codecpar->codec_id == AV_CODEC_ID_LCEVC)
stg->params.lcevc->lcevc_index = stg->nb_streams - 1;
break;
case AV_STREAM_GROUP_PARAMS_DOLBY_VISION:
if (j == 0) {
stg->params.layered_video->width = grp->streams[j]->codecpar->width;
stg->params.layered_video->height = grp->streams[j]->codecpar->height;
}
if (j == grp->nb_streams - 1)
stg->params.layered_video->el_index = stg->nb_streams - 1;
break;
default:
av_unreachable("Invalid group type!");
}
}
}
}
@@ -2791,8 +2904,10 @@ static void pmt_cb(MpegTSFilter *filter, const uint8_t *section, int section_len
mpegts_open_pcr_filter(ts, pcr_pid);
out:
if (prg)
if (prg) {
detect_bdmv_dovi_group(ts, prg, prog_reg_desc);
create_stream_groups(ts, prg);
}
for (i = 0; i < mp4_descr_count; i++)
av_free(mp4_descr[i].dec_config_descr);
+1
View File
@@ -73,6 +73,7 @@
#define M2TS_PMT_PID 0x0100
#define M2TS_PCR_PID 0x1001
#define M2TS_VIDEO_PID 0x1011
#define M2TS_VIDEO_EL_PID 0x1015
#define M2TS_AUDIO_START_PID 0x1100
#define M2TS_PGSSUB_START_PID 0x1200
#define M2TS_TEXTSUB_PID 0x1800