[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