Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ffef5f8
video/img_format: make mp_imgfmt_desc_get_num_comps input param const
kasper93 May 17, 2026
351f76a
demux: propagate track selection only from base track
kasper93 May 16, 2026
ab74951
loadfile: reselect dependent tracks only when selected by user
kasper93 May 17, 2026
71ca9e3
mp_image: add enhancement_layer child image
kasper93 May 16, 2026
87fa640
vf_format: add `vf=format=enhancement-layer` option
kasper93 May 16, 2026
b613fbd
mp_image: add no_dovi / no_enhancement_layer params
kasper93 Jun 1, 2026
be6b3db
f_enhancement_pair: add filter to match frames of separate video tracks
kasper93 May 16, 2026
0206459
f_output_chain: pair BL+EL and output as single frame
kasper93 May 17, 2026
23c8659
vo_gpu_next: parametrize frame upload/map
kasper93 May 16, 2026
00d98ba
vo_gpu_next: upload/map EL frame
kasper93 May 16, 2026
65b3e57
demux/dovi_split: add helper to split interleaved HEVC with BL and EL
kasper93 May 17, 2026
79a1f67
demux_lavf: add support for splitting EL track from in-band
kasper93 May 17, 2026
aa0a132
demux_mkv: add support for splitting EL track from in-band
kasper93 May 17, 2026
2425834
demux_lavf: handle AV_STREAM_GROUP_PARAMS_DOLBY_VISION
kasper93 May 17, 2026
41d093f
demux_disc: mirror lavf demuxer stream groups to parent
kasper93 Jun 1, 2026
ba4c5e5
demux_mkv: group EL+BL dovi tracks
kasper93 May 17, 2026
9e49c41
vo: bump size of pass desc string
kasper93 Jun 1, 2026
08497be
mp_image: don't use deprecated pl_avdovi_metadata_supported
kasper93 Jun 1, 2026
fb39a3c
demux_mkv: map hvcE into side-data
kasper93 Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DOCS/interface-changes/el.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add `vf=format=enhancement-layer` option
6 changes: 6 additions & 0 deletions DOCS/man/vf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,12 @@ Available mpv-only filters are:
Whether or not to include HDR10+ metadata (default: yes). If
disabled, any HDR10+ metadata will be stripped from frames.

``<enhancement-layer=yes|no>``
Whether or not to apply the image enhancement layer (default: yes).
If disabled, the enhancement-layer frame paired with each base-layer
frame is discarded. Currently this controls Dolby Vision Profile 7 FEL
application.

``<min-luma>``
Set the minimum luminance value for the mastering display metadata.
This is a float value in nits (cd/m²).
Expand Down
4 changes: 2 additions & 2 deletions demux/demux.c
Original file line number Diff line number Diff line change
Expand Up @@ -4145,11 +4145,11 @@ void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
struct demux_internal *in = demuxer->in;
mp_mutex_lock(&in->lock);
bool changed = select_track(in, stream, ref_pts, selected);
if (stream->group) {
if (stream->group && !stream->dependent_track) {
for (int i = 0; i < stream->group->num_members; i++) {
struct sh_stream *m = stream->group->members[i];
mp_assert(m);
if (m != stream)
if (m != stream && m->dependent_track)
changed |= select_track(in, m, ref_pts, selected);
}
}
Expand Down
29 changes: 28 additions & 1 deletion demux/demux_disc.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,9 @@ static void add_dvd_streams(demuxer_t *demuxer)
static void add_streams(demuxer_t *demuxer)
{
struct priv *p = demuxer->priv;
int old_num = p->num_streams;

for (int n = p->num_streams; n < demux_get_num_stream(p->slave); n++) {
for (int n = old_num; n < demux_get_num_stream(p->slave); n++) {
struct sh_stream *src = demux_get_stream(p->slave, n);
if (src->type == STREAM_SUB) {
struct sh_stream *sub = NULL;
Expand All @@ -142,6 +143,7 @@ static void add_streams(demuxer_t *demuxer)
// Copy all stream fields that might be relevant
*sh->codec = *src->codec;
sh->demuxer_id = src->demuxer_id;
sh->dependent_track = src->dependent_track;
if (src->type == STREAM_VIDEO) {
double ar;
if (stream_control(demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar)
Expand All @@ -157,6 +159,31 @@ static void add_streams(demuxer_t *demuxer)
get_disc_lang(demuxer->stream, sh, p->is_dvd);
demux_add_sh_stream(demuxer, sh);
}

// Mirror slave sh_stream_group onto the disc-level sh_streams. This is needed
// for the Dolby Vision BL+EL group, it's detected well by lavf. We could use
// the libbluray `dv_streams[]` info, but it's not available yet in release
// version, and mapping it through lavf is less code.
for (int n = old_num; n < p->num_streams; n++) {
struct sh_stream *disc_sh = p->streams[n];
if (!disc_sh || disc_sh->group)
continue;
struct sh_stream *src = demux_get_stream(p->slave, n);
if (!src || !src->group)
continue;
struct sh_stream_group *grp = talloc_zero(disc_sh, struct sh_stream_group);
for (int m = 0; m < src->group->num_members; m++) {
struct sh_stream *sh = src->group->members[m];
if (!sh || sh->index < 0 || sh->index >= p->num_streams)
continue;
struct sh_stream *disc_member = p->streams[sh->index];
if (!disc_member)
continue;
MP_TARRAY_APPEND(grp, grp->members, grp->num_members, disc_member);
disc_member->group = grp;
}
}

reselect_streams(demuxer);
}

Expand Down
106 changes: 105 additions & 1 deletion demux/demux_lavf.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
#include "stream/stream_curl.h"

#include "demux.h"
#include "dovi_split.h"
#include "stheader.h"
#include "options/m_config.h"
#include "options/m_option.h"
Expand Down Expand Up @@ -224,6 +225,7 @@ struct stream_info {
double last_key_pts;
double highest_pts;
double ts_offset;
struct mp_dovi_split *dovi_split;
};

typedef struct lavf_priv {
Expand Down Expand Up @@ -252,6 +254,8 @@ typedef struct lavf_priv {

int retry_counter;

struct demux_packet *pending_pkt;

AVDictionary *av_opts;

// Proxying nested streams.
Expand Down Expand Up @@ -600,6 +604,12 @@ static void select_tracks(struct demuxer *demuxer, int start)
AVStream *st = priv->avfc->streams[n];
bool selected = stream && demux_stream_is_selected(stream) &&
!stream->attached_picture;
if (!selected && priv->streams[n]->dovi_split) {
struct sh_stream *el =
mp_dovi_split_el_stream(priv->streams[n]->dovi_split);
if (el && demux_stream_is_selected(el))
selected = true;
}
st->discard = selected ? AVDISCARD_DEFAULT : AVDISCARD_ALL;
}
}
Expand Down Expand Up @@ -773,6 +783,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
sh->codec->dovi = true;
sh->codec->dv_profile = cfg->dv_profile;
sh->codec->dv_level = cfg->dv_level;
sh->codec->dv_el_present = cfg->bl_present_flag && cfg->el_present_flag;
}

// AVI uses decode-order indices as DTS and needs the compensation.
Expand Down Expand Up @@ -1266,6 +1277,42 @@ static void handle_lcevc_group(demuxer_t *demuxer, AVStreamGroup *stg)
}
#endif

#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(62, 19, 100)
// Base layer + Enhancement layer separate track stream group
static void handle_layered_video_group(demuxer_t *demuxer, AVStreamGroup *stg)
{
lavf_priv_t *priv = demuxer->priv;
AVStreamGroupLayeredVideo *layered = stg->params.layered_video;

if (stg->nb_streams != 2 || layered->el_index >= stg->nb_streams) {
MP_WARN(demuxer, "Dolby Vision group %u: expected 2 streams with valid "
"el_index, got %u streams and el_index %u\n",
stg->index, stg->nb_streams, layered->el_index);
return;
}

AVStream *el_st = stg->streams[layered->el_index];
AVStream *bl_st = stg->streams[layered->el_index ? 0 : 1];

if ((size_t)el_st->index >= priv->num_streams || (size_t)bl_st->index >= priv->num_streams)
return;

struct sh_stream *el_sh = priv->streams[el_st->index]->sh;
struct sh_stream *bl_sh = priv->streams[bl_st->index]->sh;
if (!el_sh || !bl_sh)
return;

// Group storage is attached to the BL so its lifetime tracks the demuxer.
struct sh_stream_group *group = talloc_zero(bl_sh, struct sh_stream_group);
MP_TARRAY_APPEND(group, group->members, group->num_members, bl_sh);
MP_TARRAY_APPEND(group, group->members, group->num_members, el_sh);

bl_sh->group = group;
el_sh->group = group;
el_sh->dependent_track = true;
}
#endif

static void handle_stream_groups(demuxer_t *demuxer)
{
lavf_priv_t *priv = demuxer->priv;
Expand Down Expand Up @@ -1297,6 +1344,11 @@ static void handle_stream_groups(demuxer_t *demuxer)
case AV_STREAM_GROUP_PARAMS_LCEVC:
handle_lcevc_group(demuxer, stg);
break;
#endif
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(62, 19, 100)
case AV_STREAM_GROUP_PARAMS_DOLBY_VISION:
handle_layered_video_group(demuxer, stg);
break;
#endif
default:
MP_VERBOSE(demuxer, "Unhandled stream group type %d (index %u)\n",
Expand All @@ -1307,6 +1359,22 @@ static void handle_stream_groups(demuxer_t *demuxer)
}
#endif

static void detect_dovi_split_streams(demuxer_t *demuxer)
{
lavf_priv_t *priv = demuxer->priv;
int snapshot_count = priv->num_streams;
for (int n = 0; n < snapshot_count; n++) {
struct stream_info *info = priv->streams[n];
struct sh_stream *sh = info ? info->sh : NULL;
if (!sh || sh->type != STREAM_VIDEO || !sh->codec ||
!sh->codec->dv_el_present || sh->group)
{
continue;
}
info->dovi_split = mp_dovi_split_create(demuxer, sh);
}
}

static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
{
AVFormatContext *avfc = NULL;
Expand Down Expand Up @@ -1489,6 +1557,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(60, 19, 100)
handle_stream_groups(demuxer);
#endif
detect_dovi_split_streams(demuxer);

mp_tags_move_from_av_dictionary(demuxer->metadata, &avfc->metadata);

Expand Down Expand Up @@ -1576,6 +1645,13 @@ static bool demux_lavf_read_packet(struct demuxer *demux,
{
lavf_priv_t *priv = demux->priv;

// Companion EL packet queued by the Dolby Vision splitter on a prior call.
if (priv->pending_pkt) {
*mp_pkt = priv->pending_pkt;
priv->pending_pkt = NULL;
return true;
}

AVPacket *pkt = av_packet_alloc();
MP_HANDLE_OOM(pkt);
int r = av_read_frame(priv->avfc, pkt);
Expand Down Expand Up @@ -1604,7 +1680,14 @@ static bool demux_lavf_read_packet(struct demuxer *demux,
struct sh_stream *stream = info->sh;
AVStream *st = priv->avfc->streams[pkt->stream_index];

if (!demux_stream_is_selected(stream)) {
// Keep BL packets flowing to feed the Dolby Vision splitter when its
// virtual EL is selected, even if the BL itself isn't selected. The
// unselected BL dp gets discarded by the demuxer queue downstream.
struct sh_stream *split_el = info->dovi_split
? mp_dovi_split_el_stream(info->dovi_split)
: NULL;
bool need_for_split = split_el && demux_stream_is_selected(split_el);
if (!demux_stream_is_selected(stream) && !need_for_split) {
av_packet_free(&pkt);
return true; // don't signal EOF if skipping a packet
}
Expand Down Expand Up @@ -1666,6 +1749,13 @@ static bool demux_lavf_read_packet(struct demuxer *demux,
}
}

// Dispatch the EL view of this packet via the splitter.
if (info->dovi_split) {
struct sh_stream *el = mp_dovi_split_el_stream(info->dovi_split);
if (el && demux_stream_is_selected(el))
priv->pending_pkt = mp_dovi_split_dispatch(info->dovi_split, dp);
}

if (st->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
st->event_flags = 0;
struct mp_tags *tags = talloc_zero(NULL, struct mp_tags);
Expand All @@ -1678,6 +1768,16 @@ static bool demux_lavf_read_packet(struct demuxer *demux,
return true;
}

static void reset_dovi_split_state(demuxer_t *demuxer)
{
lavf_priv_t *priv = demuxer->priv;
TA_FREEP(&priv->pending_pkt);
for (int n = 0; n < priv->num_streams; n++) {
if (priv->streams[n] && priv->streams[n]->dovi_split)
mp_dovi_split_reset(priv->streams[n]->dovi_split);
}
}

static void demux_drop_buffers_lavf(demuxer_t *demuxer)
{
lavf_priv_t *priv = demuxer->priv;
Expand All @@ -1686,6 +1786,7 @@ static void demux_drop_buffers_lavf(demuxer_t *demuxer)
stream_drop_buffers(priv->stream);
avio_flush(priv->avfc->pb);
avformat_flush(priv->avfc);
reset_dovi_split_state(demuxer);
}

static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
Expand Down Expand Up @@ -1769,6 +1870,7 @@ static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
av_strerror(r, buf, sizeof(buf));
MP_VERBOSE(demuxer, "Seek failed (%s)\n", buf);
}
reset_dovi_split_state(demuxer);

update_read_stats(demuxer);
}
Expand Down Expand Up @@ -1802,7 +1904,9 @@ static void demux_close_lavf(demuxer_t *demuxer)
struct stream_info *info = priv->streams[n];
if (info->sh)
avcodec_parameters_free(&info->sh->codec->lav_codecpar);
TA_FREEP(&info->dovi_split);
}
TA_FREEP(&priv->pending_pkt);
if (priv->own_stream)
free_stream(priv->stream);
if (priv->av_opts)
Expand Down
Loading
Loading