[vlc-commits] [Git][videolan/vlc][master] 3 commits: es_out: scale preroll clock-origin compensation by the playback rate

Jean-Baptiste Kempf (@jbk) gitlab at videolan.org
Sun Oct 11 17:22:06 UTC 2026



Jean-Baptiste Kempf pushed to branch master at VideoLAN / VLC


Commits:
77a5cc11 by Ismail Degani at 2026-10-11T18:20:30+02:00
es_out: scale preroll clock-origin compensation by the playback rate

When buffering ends, the clock origin is computed by subtracting the
preroll, a stream duration, from a system date. At rate r, the first
frame past the preroll was due preroll * (1/r - 1) after buffering
ended: a 12s stall for a 4s preroll at 0.25x. Above 1x, the next PCR
could be reported late, which flushed the decoders and restarted
buffering.

Fixes #21890

- - - - -
d5ed41ba by Thomas Guillem at 2026-10-11T18:20:30+02:00
test: es_out: factorize the common setup

The audio tracks keep group 0: the i_group assignments in their setup
wrote to video_fmt, after the video track was added.

- - - - -
a4002895 by Thomas Guillem at 2026-10-11T18:20:30+02:00
test: es_out: check preroll at rate != 1

Without the preroll compensation fix, after a 4s preroll:
 - at 0.25x, the first frame is due 12s after buffering ends: playback
   stalls,
 - at 2x, the demuxer wakeup date is 2s in the past: the next PCR is
   reported late, which flushes the decoders and restarts buffering.

Refs #21890

- - - - -


2 changed files:

- src/input/es_out.c
- src/input/test/es_out.c


Changes:

=====================================
src/input/es_out.c
=====================================
@@ -1304,7 +1304,10 @@ static void EsOutDecodersStopBuffering(es_out_sys_t *p_sys, bool b_forced)
     /* */
     const vlc_tick_t i_current_date = EsOutIsPaused(p_sys) ? p_sys->i_pause_date : vlc_tick_now();
 
-    const vlc_tick_t update = i_current_date - i_buffering_duration;
+    /* The preroll is stream time, unlike the pts_delay margin: convert it to
+     * system time with the current rate. */
+    const vlc_tick_t update = i_current_date - i_buffering_duration
+        - (vlc_tick_t)(i_preroll_duration * (1.0 / p_sys->rate - 1.0));
 
     /* The call order of these 3 input_clock_t/vlc_clock_main_t functions is
      * important:


=====================================
src/input/test/es_out.c
=====================================
@@ -45,12 +45,14 @@
 #include "../src/input/es_out.h"
 #include "../src/input/decoder.h"
 #include "../src/input/source.h"
+#include "../src/clock/clock.h"
 
 const char vlc_module_name[] = MODULE_STRING;
 
 struct vlc_input_decoder_t {
     struct vlc_list node;
 
+    vlc_clock_t *clock;
     bool drained;
     bool started;
 };
@@ -82,12 +84,12 @@ vlc_input_decoder_t *
 vlc_input_decoder_New(vlc_object_t *parent,
                       const struct vlc_input_decoder_cfg *cfg)
 {
-    (void)cfg;
     msg_Dbg(parent, "Creating input decoder from test");
 
     struct vlc_input_decoder_t *dec = malloc(sizeof(struct vlc_input_decoder_t));
     assert(dec != NULL);
 
+    dec->clock = cfg->clock;
     dec->drained = false;
     dec->started = false;
     vlc_list_append(&dec->node, &opened_decoders);
@@ -259,10 +261,12 @@ void var_OptionParse(vlc_object_t *obj, const char *chain, bool trusted)
     (void)obj; (void)chain; (void)trusted;
 }
 
+static bool can_pace_control = false;
+
 bool input_CanPaceControl(input_thread_t *input)
 {
     (void)input;
-    return false;
+    return can_pace_control;
 }
 
 int input_GetAttachments(input_thread_t *input,
@@ -349,76 +353,116 @@ static const struct vlc_logger_operations test_logger_operations = {
     .log = LogText,
 };
 
-static void test_playback(void)
+struct test_ctx
+{
+    struct vlc_logger logger;
+    vlc_object_t *root;
+    input_item_t *item;
+    input_thread_t *input;
+    input_source_t *source;
+    struct vlc_input_es_out *out;
+};
+
+static void test_ctx_Init(struct test_ctx *ctx, float rate,
+                          vlc_tick_t pts_delay)
 {
     vlc_list_init(&opened_decoders);
-    struct vlc_logger logger = { .ops = &test_logger_operations };
+    ctx->logger = (struct vlc_logger) { .ops = &test_logger_operations };
     libvlc_priv_t *libvlc = (vlc_object_create)(NULL, sizeof(*libvlc));
-    vlc_object_t *root = &libvlc->public_data.obj;
-    root->logger = &logger;
+    ctx->root = &libvlc->public_data.obj;
+    ctx->root->logger = &ctx->logger;
 
-    var_Create(root, "clock-master", VLC_VAR_STRING);
-    var_SetString(root, "clock-master", "auto");
+    var_Create(ctx->root, "clock-master", VLC_VAR_STRING);
+    var_SetString(ctx->root, "clock-master", "auto");
 
-    var_Create(root, "captions", VLC_VAR_INTEGER);
-    var_SetInteger(root, "captions", 608);
+    var_Create(ctx->root, "captions", VLC_VAR_INTEGER);
+    var_SetInteger(ctx->root, "captions", 608);
 
-    var_Create(root, "mouse-pause-policy", VLC_VAR_STRING);
-    var_SetString(root, "mouse-pause-policy", "auto");
+    var_Create(ctx->root, "mouse-pause-policy", VLC_VAR_STRING);
+    var_SetString(ctx->root, "mouse-pause-policy", "auto");
 
-    input_item_t *item = input_item_NewStream("mock://", "mock", 0);
+    ctx->item = input_item_NewStream("mock://", "mock", 0);
 
-    input_thread_private_t *priv = vlc_object_create(root, sizeof(*priv));
+    input_thread_private_t *priv = vlc_object_create(ctx->root, sizeof(*priv));
     assert(priv != NULL);
-    priv->p_item = item;
-
-    input_thread_t *input = &priv->input;
-    var_Create(input, "video", VLC_VAR_BOOL);
-    var_SetBool(input, "video", true);
-    var_Create(input, "audio", VLC_VAR_BOOL);
-    var_SetBool(input, "audio", true);
-
-    input_source_t *source = InputSourceNew();
-
-    struct vlc_input_es_out *out =
-        input_EsOutNew(input, source, 1.f, INPUT_TYPE_PLAYBACK);
-    assert(out != NULL);
-
-    es_out_SetMode(out, ES_OUT_MODE_AUTO);
-
-    vlc_tick_t pts_delay = VLC_TICK_FROM_MS(300);
-    es_out_SetJitter(out, pts_delay, 0, 40 * pts_delay / DEFAULT_PTS_DELAY);
-
-    es_format_t video_fmt;
-    es_format_Init(&video_fmt, VIDEO_ES, VLC_CODEC_RGBA);
-    video_fmt.i_id = 1;
-    video_fmt.i_group = 1;
-    video_format_Init(&video_fmt.video, VLC_CODEC_RGBA);
-    video_fmt.video.i_width = video_fmt.video.i_visible_width = 64;
-    video_fmt.video.i_height = video_fmt.video.i_visible_height = 64;
-    es_out_id_t *video_track = es_out_Add(&out->out, &video_fmt);
-    assert(video_track != NULL);
-    es_format_Clean(&video_fmt);
-
-    es_format_t audio_fmt;
-    es_format_Init(&audio_fmt, AUDIO_ES, VLC_CODEC_F32L);
-    audio_fmt.i_id = 2;
-    video_fmt.i_group = 1;
-    audio_fmt.audio.i_format = VLC_CODEC_F32L;
-    audio_fmt.audio.i_rate = 44100;
-    audio_fmt.audio.i_physical_channels = 2;
-    audio_fmt.audio.i_channels = 2;
-    es_out_id_t *audio_track = es_out_Add(&out->out, &audio_fmt);
-    assert(audio_track != NULL);
-    es_format_Clean(&audio_fmt);
+    priv->p_item = ctx->item;
+
+    ctx->input = &priv->input;
+    var_Create(ctx->input, "video", VLC_VAR_BOOL);
+    var_SetBool(ctx->input, "video", true);
+    var_Create(ctx->input, "audio", VLC_VAR_BOOL);
+    var_SetBool(ctx->input, "audio", true);
 
+    ctx->source = InputSourceNew();
+
+    ctx->out = input_EsOutNew(ctx->input, ctx->source, rate,
+                              INPUT_TYPE_PLAYBACK);
+    assert(ctx->out != NULL);
+
+    es_out_SetMode(ctx->out, ES_OUT_MODE_AUTO);
+    es_out_SetJitter(ctx->out, pts_delay, 0,
+                     40 * pts_delay / DEFAULT_PTS_DELAY);
+}
+
+static void test_ctx_Destroy(struct test_ctx *ctx)
+{
+    es_out_SetMode(ctx->out, ES_OUT_MODE_END);
+    es_out_Delete(&ctx->out->out);
+
+    input_source_Release(ctx->source);
+    input_item_Release(ctx->item);
+    vlc_object_delete(&ctx->input->obj);
+    vlc_object_delete(ctx->root);
+}
+
+static es_out_id_t *AddVideoTrack(struct vlc_input_es_out *out)
+{
+    es_format_t fmt;
+    es_format_Init(&fmt, VIDEO_ES, VLC_CODEC_RGBA);
+    fmt.i_id = 1;
+    fmt.i_group = 1;
+    video_format_Init(&fmt.video, VLC_CODEC_RGBA);
+    fmt.video.i_width = fmt.video.i_visible_width = 64;
+    fmt.video.i_height = fmt.video.i_visible_height = 64;
+    es_out_id_t *track = es_out_Add(&out->out, &fmt);
+    assert(track != NULL);
+    es_format_Clean(&fmt);
+    return track;
+}
+
+static es_out_id_t *AddAudioTrack(struct vlc_input_es_out *out)
+{
+    es_format_t fmt;
+    es_format_Init(&fmt, AUDIO_ES, VLC_CODEC_F32L);
+    fmt.i_id = 2;
+    fmt.audio.i_format = VLC_CODEC_F32L;
+    fmt.audio.i_rate = 44100;
+    fmt.audio.i_physical_channels = 2;
+    fmt.audio.i_channels = 2;
+    es_out_id_t *track = es_out_Add(&out->out, &fmt);
+    assert(track != NULL);
+    es_format_Clean(&fmt);
+    return track;
+}
+
+static void SendBlock(struct vlc_input_es_out *out, es_out_id_t *track)
+{
     block_t *block = block_Alloc(10);
     assert(block);
-    es_out_Send(&out->out, video_track, block);
+    es_out_Send(&out->out, track, block);
+}
 
-    block = block_Alloc(10);
-    assert(block);
-    es_out_Send(&out->out, audio_track, block);
+static void test_playback(void)
+{
+    struct test_ctx ctx;
+    test_ctx_Init(&ctx, 1.f, VLC_TICK_FROM_MS(300));
+    struct vlc_input_es_out *out = ctx.out;
+
+    es_out_id_t *video_track = AddVideoTrack(out);
+    es_out_id_t *audio_track = AddAudioTrack(out);
+
+    SendBlock(out, video_track);
+    SendBlock(out, audio_track);
 
     es_out_SetPCR(&out->out, VLC_TICK_0);
     es_out_SetPCR(&out->out, VLC_TICK_0 + VLC_TICK_FROM_MS(100));
@@ -452,86 +496,20 @@ static void test_playback(void)
     es_out_Del(&out->out, audio_track);
     es_out_Del(&out->out, video_track);
 
-    es_out_SetMode(out, ES_OUT_MODE_END);
-    es_out_Delete(&out->out);
-
-    input_source_Release(source);
-    input_item_Release(item);
-    vlc_object_delete(&input->obj);
-    vlc_object_delete(root);
-
+    test_ctx_Destroy(&ctx);
 }
 
 static void test_multiple_programs(void)
 {
-    vlc_list_init(&opened_decoders);
-    struct vlc_logger logger = { .ops = &test_logger_operations };
-    libvlc_priv_t *libvlc = (vlc_object_create)(NULL, sizeof(*libvlc));
-    vlc_object_t *root = &libvlc->public_data.obj;
-    root->logger = &logger;
-
-    var_Create(root, "clock-master", VLC_VAR_STRING);
-    var_SetString(root, "clock-master", "auto");
-
-    var_Create(root, "captions", VLC_VAR_INTEGER);
-    var_SetInteger(root, "captions", 608);
+    struct test_ctx ctx;
+    test_ctx_Init(&ctx, 1.f, VLC_TICK_FROM_MS(300));
+    struct vlc_input_es_out *out = ctx.out;
 
-    var_Create(root, "mouse-pause-policy", VLC_VAR_STRING);
-    var_SetString(root, "mouse-pause-policy", "auto");
+    es_out_id_t *video_track = AddVideoTrack(out);
+    es_out_id_t *audio_track = AddAudioTrack(out);
 
-    input_item_t *item = input_item_NewStream("mock://", "mock", 0);
-
-    input_thread_private_t *priv = vlc_object_create(root, sizeof(*priv));
-    assert(priv != NULL);
-    priv->p_item = item;
-
-    input_thread_t *input = &priv->input;
-    var_Create(input, "video", VLC_VAR_BOOL);
-    var_SetBool(input, "video", true);
-    var_Create(input, "audio", VLC_VAR_BOOL);
-    var_SetBool(input, "audio", true);
-
-    input_source_t *source = InputSourceNew();
-
-    struct vlc_input_es_out *out =
-        input_EsOutNew(input, source, 1.f, INPUT_TYPE_PLAYBACK);
-    assert(out != NULL);
-
-    es_out_SetMode(out, ES_OUT_MODE_AUTO);
-
-    vlc_tick_t pts_delay = VLC_TICK_FROM_MS(300);
-    es_out_SetJitter(out, pts_delay, 0, 40 * pts_delay / DEFAULT_PTS_DELAY);
-
-    es_format_t video_fmt;
-    es_format_Init(&video_fmt, VIDEO_ES, VLC_CODEC_RGBA);
-    video_fmt.i_id = 1;
-    video_fmt.i_group = 1;
-    video_format_Init(&video_fmt.video, VLC_CODEC_RGBA);
-    video_fmt.video.i_width = video_fmt.video.i_visible_width = 64;
-    video_fmt.video.i_height = video_fmt.video.i_visible_height = 64;
-    es_out_id_t *video_track = es_out_Add(&out->out, &video_fmt);
-    assert(video_track != NULL);
-    es_format_Clean(&video_fmt);
-
-    es_format_t audio_fmt;
-    es_format_Init(&audio_fmt, AUDIO_ES, VLC_CODEC_F32L);
-    audio_fmt.i_id = 2;
-    video_fmt.i_group = 2;
-    audio_fmt.audio.i_format = VLC_CODEC_F32L;
-    audio_fmt.audio.i_rate = 44100;
-    audio_fmt.audio.i_physical_channels = 2;
-    audio_fmt.audio.i_channels = 2;
-    es_out_id_t *audio_track = es_out_Add(&out->out, &audio_fmt);
-    assert(audio_track != NULL);
-    es_format_Clean(&audio_fmt);
-
-    block_t *block = block_Alloc(10);
-    assert(block);
-    es_out_Send(&out->out, video_track, block);
-
-    block = block_Alloc(10);
-    assert(block);
-    es_out_Send(&out->out, audio_track, block);
+    SendBlock(out, video_track);
+    SendBlock(out, audio_track);
 
     es_out_Control(&out->out, ES_OUT_SET_GROUP_PCR, 1, VLC_TICK_0);
     es_out_Control(&out->out, ES_OUT_SET_GROUP_PCR, 1, VLC_TICK_0 + VLC_TICK_FROM_MS(100));
@@ -541,18 +519,66 @@ static void test_multiple_programs(void)
     es_out_Del(&out->out, audio_track);
     es_out_Del(&out->out, video_track);
 
-    es_out_SetMode(out, ES_OUT_MODE_END);
-    es_out_Delete(&out->out);
+    test_ctx_Destroy(&ctx);
+}
+
+static void test_preroll_rate(float rate)
+{
+    /* es_out only computes a wakeup date when the input controls its pace */
+    can_pace_control = true;
+
+    /* Default file-caching */
+    const vlc_tick_t pts_delay = VLC_TICK_FROM_SEC(1);
+
+    struct test_ctx ctx;
+    test_ctx_Init(&ctx, rate, pts_delay);
+    struct vlc_input_es_out *out = ctx.out;
+
+    es_out_id_t *video_track = AddVideoTrack(out);
+    SendBlock(out, video_track);
+
+    /* Precise seek: the demuxer restarts 4s before the target */
+    const vlc_tick_t target = VLC_TICK_0 + VLC_TICK_FROM_SEC(4);
+    es_out_Control(&out->out, ES_OUT_SET_NEXT_DISPLAY_TIME, target);
+
+    const vlc_tick_t before = vlc_tick_now();
+    for (vlc_tick_t pcr = VLC_TICK_0; pcr <= target + pts_delay;
+         pcr += VLC_TICK_FROM_MS(100))
+        es_out_SetPCR(&out->out, pcr);
+
+    struct vlc_input_decoder_t *dec =
+        vlc_list_first_entry_or_null(&opened_decoders,
+                                     struct vlc_input_decoder_t, node);
+    assert(dec != NULL);
+    assert(dec->started);
+
+    /* The first frame past the preroll is due right away */
+    vlc_clock_Lock(dec->clock);
+    const vlc_tick_t now = vlc_tick_now();
+    const vlc_tick_t date =
+        vlc_clock_ConvertToSystem(dec->clock, now, target, rate, NULL);
+    vlc_clock_Unlock(dec->clock);
+    assert(date >= now);
+    assert(date < now + pts_delay);
+
+    /* The demuxer is due to read now, not more than pts_delay ago, which
+     * would make the next PCR late */
+    const vlc_tick_t wakeup = es_out_GetWakeup(out);
+    assert(wakeup <= vlc_tick_now());
+    assert(wakeup >= before - pts_delay);
+
+    es_out_Del(&out->out, video_track);
+
+    test_ctx_Destroy(&ctx);
 
-    input_source_Release(source);
-    input_item_Release(item);
-    vlc_object_delete(&input->obj);
-    vlc_object_delete(root);
+    can_pace_control = false;
 }
 
 int main(void)
 {
     test_playback();
     test_multiple_programs();
+    test_preroll_rate(0.25f);
+    test_preroll_rate(2.f);
     return 0;
 }



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/0fddbac8470745c5530e0b37ca0d5b581817ac68...a40028950c3a7d017049d961345414925af00908

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/0fddbac8470745c5530e0b37ca0d5b581817ac68...a40028950c3a7d017049d961345414925af00908
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help




More information about the vlc-commits mailing list