[vlc-commits] [Git][videolan/vlc][master] 28 commits: clock: remove extra line
Felix Paul Kühne (@fkuehne)
gitlab at videolan.org
Sat Apr 12 15:40:05 UTC 2025
Felix Paul Kühne pushed to branch master at VideoLAN / VLC
Commits:
5b3724c8 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: remove extra line
- - - - -
ea2750ff by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
test: clock: specify start date for run tests
Make sure the start date is always defined for CLOCK_SCENARIO_RUN tests
so that the proper first PCR is defined.
- - - - -
5fa84df1 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
test: store input vlc_clock
The input vlc_clock will be used to define start_date for the playback
timeline, when vlc_clock_Start() operation will be introduced.
- - - - -
02e7a863 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
test: clock: increase time difference
Using 100 ticks instead of 1 tick allows differentiating
off-by-VLC_TICK_0 errors from pause not working correctly.
- - - - -
eabf767a by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
test: clock: add logging
- - - - -
7ed71b86 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
es_out: remove wake-up delay
The delay is added to specify some margin for the start but we actually
want to register the real startup date, since it will be accounted to
startup the monotonic clock and prepare the playback offset from the
input clock.
Co-authored-by: Thomas Guillem <thomas at gllm.fr>
- - - - -
f0e42789 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
test_log: move to stderr
Logging to stdout makes weird log interlacing with console logger which
outputs to stderr, and makes it less easy to track synchronize in the
test log file.
- - - - -
6b4f8837 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
es_out: reset main clock with input clock
Currently, the main clock is reset once the new buffering is done. This
buffering step will happen at the following locations:
- at the beginning of a playback
- when a seek is triggered by the user
- when the demux needs to trigger RESET_PCR
But the input clock was already reset at this location and will start to
provide new points. Resetting the clock there as well as when the
buffering is done currently changes nothing because we either have
already flushed everything (seek or RESET_PCR) or we are not running
through a clock (frame-by-frame mode).
However, future patches will remove the clock reset at the end of the
buffering and expect the clock to be already reset, to introduce a start
function for the clock.
Co-authored-by: Thomas Guillem <thomas at gllm.fr>
- - - - -
2682cfca by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: factor the offset computation
We'll be changing the way the offset is computed, and reproduce the
computation at multiple locations. Factoring the computation in a
function will allow to simplify the rest of the changes.
- - - - -
47e7a4d2 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: implement vlc_clock_Start
This commit creates a new way in the clock system to intercept the
behaviour of the "startup" depending on whether the clock is for the
input or the output and whether it's a reference clock or a derived one.
Allowing to specify this behaviour contributes to unify the usage of the
clock, so that it's easier to use in the future (think start() with the
current time and startup PCR as planned vs SetFirstPCR() with a
corrected startup time and the last PCR received). It also contributes
to improve the cases where the input is a reference clock and paced by
the source.
The es_out is handling the current startup sequence for the clock by
resetting the clock, shifting the input clock and setting the startup
time again. Those functions are mixing three different components and
behaviour and makes it hard to test the startup scenarios.
vlc_clock_Start() on the input vlc_clock_t will replace those calls by
providing the correct offset on the output points instead of
transforming the input points.
In addition, vlc_clock_Start() will be provided to the output clocks so
that they can provide the wait_sync reference point, instead of relying
on the first conversion done, and specify the exact startup time for the
output clocks by computing the additional delay at the moment the
playback really starts.
Like first_pcr, start_time must be shifted when pause is set. It would
also be possible to "reset" the start_time when un-pausing to the
un-pause date.
Note that this commit doesn't change the behaviour yet, and the
consequences it brings will be seen when SetFirstPCR is effectively
removed. Current case coverage is also quite low in tests, so failures
would happen without SetFirstPCR in a random way.
Co-authored-by: Thomas Guillem <thomas at gllm.fr>
- - - - -
90a94a03 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: reset by computing offset from coeff
Resetting the main_clock breaks the clock context information,
especially regarding startup and monotonic synchronization. We only want
to reset the coefficient to 1.f, and recompute the offset accordingly.
- - - - -
67ec80ac by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: use 0 instead of VLC_TICK_INVALID for offset
Offset is a time difference, so it should really be initialized to 0 and
not VLC_TICK_INVALID.
- - - - -
0ec64c5b by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: clock_master: report 0 as drift
...instead of VLC_TICK_INVALID. Drift is a time difference and a
reference clock is never drifting.
- - - - -
e583c632 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: compute offset from start_time
Previously, the clock was driven by two values: coeff and offset. The
idea was that since the clock is supposed to look like an affine line,
we could extrapolate any `ts` media point into `ts * coeff + offset` in
system time.
It meant that offset could take any value depending on the difference
between system time and media time, reducing the interpretability of the
values.
After this patch, offset is computed from the start of the stream, so
it will basically only include the difference between the timestamp
computation and the real clock.
Co-authored-by: Thomas Guillem <thomas at gllm.fr>
- - - - -
324d1a55 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
test: add clock_start test
The test is extending the current clock.c test but is specifically
designed to check the clock startup scenarios. Those tests are also
matching with the "SCENARIO_RUN" case in the clock.c test.
It doesn't need the tracer infrastructure to run.
Co-authored-by: Thomas Guillem <thomas at gllm.fr>
- - - - -
27ace9d2 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: setup monotonic wait_sync_ref start in vlc_clock_Start()
Previously, calling vlc_clock_ConvertToSystem was the only way to
initialize the monotonic wait_sync_ref point which will be used to start
the outputs. The rationale for that is written in commit:
commit 491b48a6ca5a9b69f8aed25455f8e11574f32a7b
Author: Thomas Guillem <thomas at gllm.fr>
Date: Thu Oct 10 17:25:51 2019 +0200
clock: fix false start with some SPU tracks
Reminder of the design: Any calls to vlc_clock_ConvertToSystem() or
vlc_clock_Update() will create the reference point (wait_sync_ref) if it is the
first call of all sub clocks. This point will be used as a reference by all
clocks until the master does its first vlc_clock_Update().
Creating a reference point from vlc_clock_ConvertToSystem() can be discussed.
You don't expect to modify the main_clock from a function with such name
("convert"). The pro of this design is that vlc_clock_ConvertToSystem() can't
fail and will always return a valid point. Returning a valid point is
mandatory for the actual design, let's see how the audio output is using it:
The aout will first call vlc_clock_ConvertToSystem(ts) to get the first system
date of the ts, this first system date will take into account the output
dejitter, the input dejitter and the pcr_delay int and will be very likely in
the future. Enough in the future to wait for any other tracks. When this system
date is reached by the aout plugin, the aout will call the first update() and
will drive all other clocks.
...
Nowadays, two startup modes can be highlighted for the clock:
- Either the input clock drives the clock bus and we have a reference
point from the start of the stream in the next patches, so we don't
use the monotonic start to setup the first clock points.
- Or one of the outputs will drive the clock bus and we setup the first
time conversion for those output depending on the input start, the
clock priorities (added in 491b48a6ca5a9b69f8aed25455f8e11574f32a7b).
In the second point, it can only happen at the beginning of the
elementary stream, or after a proper flush/seek. By calling a
vlc_clock_Start function instead, we make it clearer that some setup
will be done, and we can log those points to trace the startup
conditions.
The code is extracted from the current vlc_clock_ConvertToSystem startup
mechanism but has been heavily documented to replace the "Fixup: remove"
comment.
Co-authored-by: Thomas Guillem <thomas at gllm.fr>
- - - - -
b48fc809 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
test: clock: call SetFirstPCR in convert_paused_common
The initialization of the clock was missing in the test, which was not
an issue before but will be in the next commits.
- - - - -
7a9cc359 by Thomas Guillem at 2025-04-12T15:24:45+00:00
clock: don't create wait_sync_ref from slave updates
We want every outputs to update their points, but they might be forced
(like the first video picture). Since a valid update follow a convert,
we prefer to only create wait_sync_ref from convert.
This will be clear with the upcoming vlc_clock_Start rework (the
wait_sync_ref point will be always created from this upcoming function).
Co-authored-by: Alexandre Janniaux <ajanni at videolabs.io>
- - - - -
489a2031 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: remove wait_sync_ref assignment in slave_reset
wait_sync_ref should not be reset when resetting a derived clock. The
clock bus should instead accept multiple possible time and choose the
best start time for the given context, and reset() should only allow the
clock to switch context.
Co-authored-by: Thomas Guillem <thomas at gllm.fr>
- - - - -
7c4157a6 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
input: es_out: use specific clock reset instead of clock-bus
vlc_clock_main_Reset() breaks the information inside clock context and
it makes tracking the context switching harder. By using the
clock-specific reset function, we'll be able to setup the behaviour of
the input clock being reset and handle the creation of a new context
instead.
- - - - -
3614593f by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: setup input-specific clock reset operations
No functional changes for now. The function will be implemented
differently for the input vlc_clock, so that it always a source of new
clock context, while the reset of the output clocks will allow selecting
a newer clock context insteand.
- - - - -
20caec57 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: setup new clock_context selection
Ensure that every clocks (even input) has a valid clock_context, and
specifically select them during vlc_clock_Start(), and un-select them
during vlc_clock_Reset().
The previous mode is still present in the code to avoid breaking the
current code until all the necessary changes are introduced, and is
selected by whether the main_clock->first_pcr value has been set or not.
Now that vlc_clock_main_Reset() is not called anymore at this location,
the points registered from the input when it is driving the clock bus
will not be removed.
/!\ As a consequence, --clock-master=input now starts from the input
/!\ clock points instead of starting from a monotonic clock point.
This indirectly fixes an issue where the output_dejitter is accounted in
computing the wait_sync_ref date, but also making all the outputs late
compared to the input clock, leading to "Deferring start" messages from
the audio output, and then "Starting late" message.
Co-authored-by: Thomas Guillem <thomas at gllm.fr>
- - - - -
eb480995 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: store start date in last conversion
This fixes a lipsync issue when a context is created from the beginning
of the stream.
- - - - -
95a5e14c by Thomas Guillem at 2025-04-12T15:24:45+00:00
decoder: start clock only once and even when not buffered
Not buffered can happen if a decoder output a first frame late (after buffering)
- - - - -
56327ed9 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
input: decoder: start clock when decoders are ready
DecoderWaitUnblock is waiting for the vlc_input_decoder_StopWait which
will be called by the es_out when every decoders are ready to start
playing.
When DecoderWaitUnblock is called and almost ready to return, it means
that we already have a frame in the output and that playback has
started. We can compute the monotonic start by calling vlc_clock_Start.
Some care must be taken given that other reasons might unblock the
decoders, like flushing or closing the decoder.
Currently, vlc_clock_Start() does nothing as long as we didn't start the
input vlc_clock_t also, but all of the code will be enabled after.
Co-authored-by: Thomas Guillem <thomas at gllm.fr>
- - - - -
4d1fcf56 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
test: clock: adapt after previous changes
Clock must now be started from a dedicated input vlc_clock. The start is
not yet mandatory but is available and will be mandatory after next
patches.
Previously, the clock was driven by two values: coeff and offset. The
idea was that since the clock is supposed to look like an affine line,
we could extrapolate any `ts` media point into `ts * coeff + offset` in
system time.
It meant that offset could take any value depending on the difference
between system time and media time, reducing the interpretability of the
values.
After this patch, offset is computed from the start of the stream in the
test, so it will basically only include the difference between the
timestamp computation and the real clock.
Co-authored-by: Thomas Guillem <thomas at gllm.fr>
- - - - -
b295062d by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: don't generate wait_sync_ref with clock-start
The conversion function might not have wait_sync_ref defined in the
next steps as it won't generate the wait_sync_ref itself, but choosing
the start_date requires transforming the current point.
The assertion on wait_sync_ref is removed given that it would be NULL
when using start_time.
Co-authored-by: Thomas Guillem <thomas at gllm.fr>
- - - - -
ae286927 by Alexandre Janniaux at 2025-04-12T15:24:45+00:00
clock: prefer start_time over first_pcr
This prioritize using the start_time path if start_time has been defined
regardless of whether vlc_clock_SetFirstPcr has been called or not,
instead of using the first_pcr path as long as first_pcr has been
defined.
- - - - -
9 changed files:
- src/clock/clock.c
- src/clock/clock.h
- src/input/decoder.c
- src/input/es_out.c
- test/Makefile.am
- test/libvlc/test.h
- test/src/clock/clock.c
- + test/src/clock/clock_start.c
- test/src/meson.build
Changes:
=====================================
src/clock/clock.c
=====================================
@@ -55,6 +55,12 @@ struct vlc_clock_context
clock_point_t last;
clock_point_t wait_sync_ref;
+ /**
+ * Start point emitted by the buffering to indicate when we supposedly
+ * started the playback. It does not account for latency of the output
+ * when one of them is driving the main_clock. */
+ clock_point_t start_time;
+
struct vlc_list node;
struct vlc_list using_clocks;
};
@@ -83,6 +89,7 @@ struct vlc_clock_main_t
unsigned wait_sync_ref_priority;
clock_point_t first_pcr;
+
vlc_tick_t output_dejitter; /* Delay used to absorb the output clock jitter */
vlc_tick_t input_dejitter; /* Delay used to absorb the input jitter */
@@ -99,6 +106,21 @@ struct vlc_clock_ops
vlc_tick_t (*set_delay)(vlc_clock_t *clock, vlc_tick_t delay);
vlc_tick_t (*to_system)(vlc_clock_t *clock, struct vlc_clock_context *ctx,
vlc_tick_t system_now, vlc_tick_t ts, double rate);
+
+ /**
+ * Signal the clock bus that the given clock will start a new timeline.
+ *
+ * When starting an input clock, a whole new context is created to track
+ * the parameters for the new timeline.
+ *
+ * When starting an output clock, the given clock will track the new context
+ * to register the progress and convert the timestamps.
+ *
+ * \param clock the clock which is ready to start
+ * \param system_now the system time for the start point
+ * \param ts the time for the start point
+ **/
+ void (*start)(vlc_clock_t *clock, vlc_tick_t system_now, vlc_tick_t ts);
};
struct vlc_clock_t
@@ -201,13 +223,30 @@ static inline void TraceRender(struct vlc_tracer *tracer, const char *type,
VLC_TRACE_END);
}
+static vlc_tick_t ComputeOffset(vlc_clock_main_t *main,
+ struct vlc_clock_context *ctx,
+ vlc_tick_t system_now,
+ vlc_tick_t ts,
+ double rate)
+{
+ vlc_tick_t start_stream = ctx->start_time.stream - VLC_TICK_0;
+ vlc_tick_t start_system = ctx->start_time.system - VLC_TICK_0;
+ if (main->first_pcr.system != VLC_TICK_INVALID)
+ start_stream = start_system = 0;
+
+ return system_now
+ - ((vlc_tick_t) ((ts - start_stream) * ctx->coeff / rate))
+ - start_system;
+}
+
static void context_reset(struct vlc_clock_context *ctx)
{
ctx->coeff = 1.0f;
ctx->rate = 1.0f;
- ctx->offset = VLC_TICK_INVALID;
+ ctx->offset = 0;
ctx->wait_sync_ref = clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
ctx->last = clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
+ ctx->start_time = clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
}
static void context_init(struct vlc_clock_context *ctx)
@@ -231,7 +270,10 @@ static vlc_tick_t context_stream_to_system(const struct vlc_clock_context *ctx,
{
if (ctx->last.system == VLC_TICK_INVALID)
return VLC_TICK_INVALID;
- return ((vlc_tick_t) (ts * ctx->coeff / ctx->rate)) + ctx->offset;
+
+ return ((vlc_tick_t) ((ts - ctx->start_time.stream) * ctx->coeff / ctx->rate))
+ + ctx->offset
+ + ctx->start_time.system;
}
static void
@@ -373,7 +415,6 @@ static inline void vlc_clock_on_update(vlc_clock_t *clock,
system_now, ts, drift, clock->context ? clock->context->clock_id : 0);
}
-
static void vlc_clock_master_update_coeff(
vlc_clock_t *clock, struct vlc_clock_context *ctx,
vlc_tick_t system_now, vlc_tick_t ts, double rate)
@@ -420,7 +461,9 @@ static void vlc_clock_master_update_coeff(
/* Reset and continue (calculate the offset from the
* current point) */
- vlc_clock_main_reset(main_clock);
+ ctx->coeff = 1.f;
+ AvgResetAndFill(&main_clock->coeff_avg, 1.);
+ ctx->offset = ComputeOffset(main_clock, ctx, system_now, ts, rate);
}
else
{
@@ -438,7 +481,7 @@ static void vlc_clock_master_update_coeff(
vlc_clock_SendEvent(main_clock, discontinuity);
}
- ctx->offset = system_now - ((vlc_tick_t) (ts * ctx->coeff / rate));
+ ctx->offset = ComputeOffset(main_clock, ctx, system_now, ts, rate);
if (main_clock->tracer != NULL && clock->track_str_id != NULL)
vlc_tracer_Trace(main_clock->tracer,
@@ -480,7 +523,7 @@ static vlc_tick_t vlc_clock_master_update(vlc_clock_t *clock,
if (clock->delay > 0 && main_clock->delay < 0 && ts > -main_clock->delay)
ts += main_clock->delay;
- vlc_tick_t drift = VLC_TICK_INVALID;
+ vlc_tick_t drift = 0;
vlc_clock_on_update(clock, system_now, ts, drift,
rate, frame_rate, frame_rate_base);
return drift;
@@ -499,7 +542,6 @@ static void vlc_clock_master_reset(vlc_clock_t *clock)
if (clock->context != NULL && clock->context != ctx)
vlc_clock_switch_context(clock, ctx);
- vlc_clock_main_reset(main_clock);
assert(main_clock->delay <= 0);
assert(clock->delay >= 0);
@@ -547,6 +589,79 @@ static vlc_tick_t vlc_clock_master_set_delay(vlc_clock_t *clock, vlc_tick_t dela
return delta;
}
+static void
+vlc_clock_input_start(vlc_clock_t *clock,
+ vlc_tick_t start_date,
+ vlc_tick_t first_ts)
+{
+ vlc_clock_main_t *main_clock = clock->owner;
+ vlc_mutex_assert(&main_clock->lock);
+ assert(main_clock->context != NULL);
+
+ if (main_clock->first_pcr.system != VLC_TICK_INVALID)
+ return;
+
+ struct vlc_clock_context *context
+ = main_clock->context;
+
+ struct vlc_clock_context *last_context =
+ vlc_list_last_entry_or_null(&main_clock->prev_contexts, struct vlc_clock_context, node);
+
+ if (last_context != NULL)
+ context->clock_id = last_context->clock_id + 1;
+
+ // Note: should not trigger when SetFirstPCR is called
+ context->start_time = clock_point_Create(start_date, first_ts);
+ context->offset = ComputeOffset(main_clock, context, start_date, first_ts, 1.f);
+ main_clock->wait_sync_ref_priority = UINT_MAX;
+
+ vlc_clock_switch_context(clock, context);
+}
+
+static void vlc_clock_slave_reset(vlc_clock_t *clock);
+static void vlc_clock_input_reset(vlc_clock_t *clock)
+{
+ vlc_clock_main_t *main_clock = clock->owner;
+
+ if (main_clock->tracer != NULL && clock->track_str_id != NULL)
+ vlc_tracer_TraceEvent(main_clock->tracer, "RENDER", clock->track_str_id,
+ "reset_user");
+
+ if (main_clock->first_pcr.system != VLC_TICK_INVALID)
+ {
+ (main_clock->master == clock ? vlc_clock_master_reset : vlc_clock_slave_reset)(clock);
+ return;
+ }
+
+ if (clock->context == NULL || clock->context->start_time.system == VLC_TICK_INVALID)
+ return;
+
+ struct vlc_clock_context *context = main_clock->context;
+
+ bool has_other_clock = false;
+ vlc_clock_t *clock_iterator;
+ vlc_list_foreach(clock_iterator, &context->using_clocks, node)
+ {
+ if (clock_iterator == clock)
+ continue;
+
+ has_other_clock = true;
+ }
+
+ if (!has_other_clock)
+ {
+ context_reset(context);
+ return;
+ }
+
+ assert(context->start_time.stream != VLC_TICK_INVALID);
+ vlc_list_append(&context->node, &main_clock->prev_contexts);
+ main_clock->context = context_new();
+
+ if (main_clock->context == NULL)
+ main_clock->context = context; /* TODO: It fallbacks to previous context */
+}
+
static vlc_tick_t
vlc_clock_monotonic_to_system(vlc_clock_t *clock, struct vlc_clock_context *ctx,
vlc_tick_t now, vlc_tick_t ts, double rate)
@@ -554,16 +669,20 @@ vlc_clock_monotonic_to_system(vlc_clock_t *clock, struct vlc_clock_context *ctx,
vlc_clock_main_t *main_clock = clock->owner;
if (clock->priority < main_clock->wait_sync_ref_priority
- && ctx == main_clock->context)
+ && ctx == main_clock->context
+ && main_clock->first_pcr.system != VLC_TICK_INVALID)
{
/* XXX: This input_delay calculation is needed until we (finally) get
* ride of the input clock. This code is adapted from input_clock.c and
* is used to introduce the same delay than the input clock (first PTS
* - first PCR). */
- vlc_tick_t pcr_delay =
- main_clock->first_pcr.system == VLC_TICK_INVALID ? 0 :
- (ts - main_clock->first_pcr.stream) / rate +
- main_clock->first_pcr.system - now;
+ vlc_tick_t pcr_delay = 0;
+ if (ctx->start_time.system != VLC_TICK_INVALID)
+ pcr_delay = (ts - ctx->start_time.stream) / rate +
+ ctx->start_time.system - now;
+ else if (main_clock->first_pcr.system != VLC_TICK_INVALID)
+ pcr_delay = (ts - main_clock->first_pcr.stream) / rate +
+ main_clock->first_pcr.system - now;
if (pcr_delay > MAX_PCR_DELAY)
{
@@ -581,7 +700,6 @@ vlc_clock_monotonic_to_system(vlc_clock_t *clock, struct vlc_clock_context *ctx,
ctx->wait_sync_ref = clock_point_Create(now + delay, ts);
}
- assert(ctx->wait_sync_ref.stream != VLC_TICK_INVALID);
return (ts - ctx->wait_sync_ref.stream) / rate + ctx->wait_sync_ref.system;
}
@@ -627,6 +745,8 @@ static vlc_tick_t vlc_clock_slave_update(vlc_clock_t *clock,
unsigned frame_rate,
unsigned frame_rate_base)
{
+ vlc_clock_main_t *main_clock = clock->owner;
+
if (system_now == VLC_TICK_MAX)
{
/* If system_now is VLC_TICK_MAX, the update is forced, don't modify
@@ -636,9 +756,19 @@ static vlc_tick_t vlc_clock_slave_update(vlc_clock_t *clock,
return VLC_TICK_MAX;
}
- vlc_tick_t computed = clock->ops->to_system(clock, ctx, system_now, ts, rate);
+ vlc_tick_t drift;
+ vlc_tick_t computed;
+ if (main_clock->first_pcr.system != VLC_TICK_INVALID || ctx->wait_sync_ref.stream != VLC_TICK_INVALID)
+ {
+ computed = clock->ops->to_system(clock, ctx, system_now, ts, rate);
+ drift = computed - system_now;
+ }
+ else
+ {
+ computed = system_now;
+ drift = 0;
+ }
- vlc_tick_t drift = computed - system_now;
vlc_clock_on_update(clock, computed, ts, drift, rate,
frame_rate, frame_rate_base);
return drift;
@@ -653,9 +783,6 @@ static void vlc_clock_slave_reset(vlc_clock_t *clock)
if (clock->context != NULL && clock->context != ctx)
vlc_clock_switch_context(clock, ctx);
- main_clock->wait_sync_ref_priority = UINT_MAX;
- ctx->wait_sync_ref = clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
-
vlc_clock_on_update(clock, VLC_TICK_INVALID, VLC_TICK_INVALID,
VLC_TICK_INVALID, 1.0f, 0, 0);
}
@@ -670,6 +797,111 @@ static vlc_tick_t vlc_clock_slave_set_delay(vlc_clock_t *clock, vlc_tick_t delay
return 0;
}
+static void
+vlc_clock_output_start(vlc_clock_t *clock,
+ vlc_tick_t start_date,
+ vlc_tick_t first_ts)
+{
+ (void)start_date; (void)first_ts;
+
+ vlc_clock_main_t *main_clock = clock->owner;
+ vlc_mutex_assert(&main_clock->lock);
+
+ if (clock->last_conversion == VLC_TICK_INVALID)
+ clock->last_conversion = start_date;
+
+ /* Attach to the correct context in case of reset */
+ struct vlc_clock_context *context
+ = vlc_clock_get_context(clock, start_date, first_ts, true);
+
+#if 0
+ /* Disabled for now, the handling will be done later. */
+ /* vlc_clock_Start must have already been called. */
+ assert(context->start_time.system != VLC_TICK_INVALID);
+ assert(context->start_time.stream != VLC_TICK_INVALID);
+#endif
+ if (context->start_time.system == VLC_TICK_INVALID)
+ return;
+
+ /* Attach to the correct context in case of reset */
+ clock->context = context;
+
+ if (clock->priority >= main_clock->wait_sync_ref_priority)
+ return;
+
+ /**
+ * The clock should have already been started, so we have a valid
+ * start_time which links a PCR value to a time where the PCR is being
+ * "used". Any PTS from the output will necessarily be converted after
+ * this start_time, and will necessarily have bigger PTS than the
+ * registered PCR, so we can use the difference to find how much delta
+ * there is between the first PTS and the first PCR.
+ *
+ * When setting up the outputs, some amount of wait will already be
+ * consumed, for instance when playing live stream, we consume some time
+ * between the first decoded packet and the end of the buffering. The
+ * core currently handles this by substracting the buffering duration to
+ * the start time, and we re-inject this duration through the pts-delay,
+ * called input_dejitter in the clock.
+ *
+ * | Start End of Decoders ready
+ * | buffering buffering (DecoderWaitUnblock)
+ * | v v v
+ * | x-----------------------x-----------x---> system time
+ * |
+ * | Preroll and
+ * | pts delay decoder delay
+ * | |----------------------->|---------->
+ * | <-----------------------|
+ * | Buffering duration
+ * | (media time)
+ * |
+ * | Fig.1: System time representation of the clock startup
+ *
+ * The defined start_time from the input is set according to the PCR
+ * but the first PTS received by an output is likely later than this
+ * clock time, so the real output start_time must be shifted to keep
+ * intra-media synchronization in acceptable levels.
+ *
+ * | Start First audio First video
+ * | time packet PTS packet PTS
+ * | v v v
+ * | x------------x------------x----------> media time
+ * |
+ * | |------------>
+ * | Audio PCR delay
+ * |
+ * | |------------------------->
+ * | Video PCR delay
+ * |
+ * | Fig.2: Media time representation of the clock startup
+ *
+ * If the pts-delay is very low, and if the PCR delay is very small,
+ * an additional output_dejitter is used to offset the beginning of
+ * the playback in a uniform manner, to let some time for the outputs
+ * to start. It only makes sense when one of the output will drive
+ * the bus clock.
+ **/
+
+ vlc_tick_t pcr_delay = (first_ts - context->start_time.stream) / context->rate
+ + context->start_time.system - start_date;
+
+ if (pcr_delay > MAX_PCR_DELAY)
+ {
+ if (main_clock->logger != NULL)
+ vlc_error(main_clock->logger, "Invalid PCR delay ! Ignoring it...");
+ pcr_delay = 0;
+ }
+
+ const vlc_tick_t input_delay = main_clock->input_dejitter + pcr_delay;
+
+ const vlc_tick_t delay =
+ __MAX(input_delay, main_clock->output_dejitter);
+
+ main_clock->wait_sync_ref_priority = clock->priority;
+ context->wait_sync_ref = clock_point_Create(start_date + delay, first_ts);
+}
+
void vlc_clock_Lock(vlc_clock_t *clock)
{
vlc_clock_main_t *main_clock = clock->owner;
@@ -740,6 +972,9 @@ vlc_clock_main_t *vlc_clock_main_New(struct vlc_logger *parent_logger, struct vl
clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
main_clock->wait_sync_ref_priority = UINT_MAX;
+ ctx->start_time =
+ clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
+
main_clock->pause_date = VLC_TICK_INVALID;
main_clock->input_dejitter = DEFAULT_PTS_DELAY;
main_clock->output_dejitter = AOUT_MAX_PTS_ADVANCE * 2;
@@ -831,10 +1066,13 @@ void vlc_clock_main_ChangePause(vlc_clock_main_t *main_clock, vlc_tick_t now,
if (ctx->last.system != VLC_TICK_INVALID)
{
ctx->last.system += delay;
- ctx->offset += delay;
+ if (ctx->start_time.system == VLC_TICK_INVALID)
+ ctx->offset += delay;
}
if (main_clock->first_pcr.system != VLC_TICK_INVALID)
main_clock->first_pcr.system += delay;
+ if (ctx->start_time.system != VLC_TICK_INVALID)
+ ctx->start_time.system += delay;
if (ctx->wait_sync_ref.system != VLC_TICK_INVALID)
ctx->wait_sync_ref.system += delay;
main_clock->pause_date = VLC_TICK_INVALID;
@@ -873,8 +1111,14 @@ vlc_tick_t vlc_clock_Update(vlc_clock_t *clock, vlc_tick_t system_now,
vlc_tick_t ts, double rate)
{
AssertLocked(clock);
- struct vlc_clock_context *ctx =
- vlc_clock_get_context(clock, system_now, ts, true);
+
+ vlc_clock_main_t *main_clock = clock->owner;
+ struct vlc_clock_context *ctx;
+
+ if (main_clock->first_pcr.system != VLC_TICK_INVALID)
+ ctx = vlc_clock_get_context(clock, system_now, ts, true);
+ else
+ ctx = clock->context;
return clock->ops->update(clock, ctx, system_now, ts, rate, 0, 0);
}
@@ -884,8 +1128,13 @@ vlc_tick_t vlc_clock_UpdateVideo(vlc_clock_t *clock, vlc_tick_t system_now,
unsigned frame_rate, unsigned frame_rate_base)
{
AssertLocked(clock);
- struct vlc_clock_context *ctx =
- vlc_clock_get_context(clock, system_now, ts, true);
+ vlc_clock_main_t *main_clock = clock->owner;
+ struct vlc_clock_context *ctx;
+
+ if (main_clock->first_pcr.system != VLC_TICK_INVALID)
+ ctx = vlc_clock_get_context(clock, system_now, ts, true);
+ else
+ ctx = clock->context;
return clock->ops->update(clock, ctx, system_now, ts, rate,
frame_rate, frame_rate_base);
@@ -923,6 +1172,7 @@ static const struct vlc_clock_ops master_ops = {
.reset = vlc_clock_master_reset,
.set_delay = vlc_clock_master_set_delay,
.to_system = vlc_clock_master_to_system,
+ .start = vlc_clock_output_start,
};
static const struct vlc_clock_ops slave_ops = {
@@ -930,6 +1180,23 @@ static const struct vlc_clock_ops slave_ops = {
.reset = vlc_clock_slave_reset,
.set_delay = vlc_clock_slave_set_delay,
.to_system = vlc_clock_slave_to_system,
+ .start = vlc_clock_output_start,
+};
+
+static const struct vlc_clock_ops input_master_ops = {
+ .update = vlc_clock_master_update,
+ .reset = vlc_clock_input_reset,
+ .set_delay = vlc_clock_master_set_delay,
+ .to_system = vlc_clock_master_to_system,
+ .start = vlc_clock_input_start,
+};
+
+static const struct vlc_clock_ops input_slave_ops = {
+ .update = vlc_clock_slave_update,
+ .reset = vlc_clock_input_reset,
+ .set_delay = vlc_clock_slave_set_delay,
+ .to_system = vlc_clock_slave_to_system,
+ .start = vlc_clock_input_start,
};
static vlc_clock_t *vlc_clock_main_Create(vlc_clock_main_t *main_clock,
@@ -949,21 +1216,22 @@ static vlc_clock_t *vlc_clock_main_Create(vlc_clock_main_t *main_clock,
clock->cbs_data = cbs_data;
clock->priority = priority;
assert(!cbs || cbs->on_update);
- clock->last_conversion = 0;
+ clock->last_conversion = VLC_TICK_INVALID;
if (input)
- clock->context = NULL; /* Always use the main one */
- else
{
- /* Attach the clock to the first context or the main one */
- struct vlc_clock_context *ctx =
- vlc_list_first_entry_or_null(&main_clock->prev_contexts,
- struct vlc_clock_context, node);
- if (likely(ctx == NULL))
- ctx = main_clock->context;
- vlc_clock_attach_context(clock, ctx);
+ /* Always use the main one */
+ vlc_clock_attach_context(clock, main_clock->context);
+ return clock;
}
+ /* Attach the clock to the first context or the main one */
+ struct vlc_clock_context *ctx =
+ vlc_list_first_entry_or_null(&main_clock->prev_contexts,
+ struct vlc_clock_context, node);
+ if (likely(ctx == NULL))
+ ctx = main_clock->context;
+ vlc_clock_attach_context(clock, ctx);
return clock;
}
@@ -1008,13 +1276,13 @@ vlc_clock_t *vlc_clock_main_CreateInputMaster(vlc_clock_main_t *main_clock)
/* Even if the master ES clock has already been created, it should not
* have updated any points */
- assert(ctx->offset == VLC_TICK_INVALID); (void) ctx;
+ assert(ctx->offset == 0); (void) ctx;
/* Override the master ES clock if it exists */
if (main_clock->master != NULL)
main_clock->master->ops = &slave_ops;
- clock->ops = &master_ops;
+ clock->ops = &input_master_ops;
main_clock->input_master = clock;
main_clock->rc++;
@@ -1031,7 +1299,7 @@ vlc_clock_t *vlc_clock_main_CreateInputSlave(vlc_clock_main_t *main_clock)
if (!clock)
return NULL;
- clock->ops = &slave_ops;
+ clock->ops = &input_slave_ops;
main_clock->rc++;
return clock;
@@ -1104,3 +1372,10 @@ void vlc_clock_Delete(vlc_clock_t *clock)
vlc_mutex_unlock(&main_clock->lock);
free(clock);
}
+
+void vlc_clock_Start(vlc_clock_t *clock,
+ vlc_tick_t start_date,
+ vlc_tick_t first_ts)
+{
+ clock->ops->start(clock, start_date, first_ts);
+}
=====================================
src/clock/clock.h
=====================================
@@ -330,4 +330,16 @@ vlc_tick_t vlc_clock_ConvertToSystem(vlc_clock_t *clock,
vlc_tick_t system_now, vlc_tick_t ts,
double rate, uint32_t *clock_id);
+/**
+ * Starts a new clock based on the given clock point, accounting for
+ * previous updates.
+ *
+ * @param clock an input or output clock that is being started
+ * @param start_date the system time at which the first update starts
+ * @param first_ts the media time origin
+ **/
+void vlc_clock_Start(vlc_clock_t *clock,
+ vlc_tick_t start_date,
+ vlc_tick_t first_ts);
+
#endif /*CLOCK_H*/
=====================================
src/input/decoder.c
=====================================
@@ -209,6 +209,7 @@ struct vlc_input_decoder_t
bool b_waiting;
bool b_first;
bool b_has_data;
+ bool out_started;
/* Flushing */
bool flushing;
@@ -1023,7 +1024,7 @@ static void RequestReload( vlc_input_decoder_t *p_owner )
atomic_compare_exchange_strong( &p_owner->reload, &expected, RELOAD_DECODER );
}
-static int DecoderWaitUnblock( vlc_input_decoder_t *p_owner )
+static int DecoderWaitUnblock(vlc_input_decoder_t *p_owner, vlc_tick_t date)
{
struct vlc_tracer *tracer = vlc_object_get_tracer(VLC_OBJECT(&p_owner->dec));
vlc_fifo_Assert(p_owner->p_fifo);
@@ -1036,16 +1037,19 @@ static int DecoderWaitUnblock( vlc_input_decoder_t *p_owner )
vlc_cond_signal( &p_owner->wait_acknowledge );
}
- bool did_wait = false;
while (p_owner->b_waiting && p_owner->b_has_data && !p_owner->flushing)
- {
vlc_fifo_WaitCond(p_owner->p_fifo, &p_owner->wait_request);
- /* We should not start the clock if we ended up flushing. */
- did_wait = !p_owner->flushing;
- }
- if (tracer != NULL && did_wait)
- vlc_tracer_TraceEvent(tracer, "DEC", p_owner->psz_id, "stop wait");
+ if (!p_owner->out_started)
+ {
+ p_owner->out_started = true;
+ if (tracer != NULL)
+ vlc_tracer_TraceEvent(tracer, "DEC", p_owner->psz_id, "stop wait");
+
+ vlc_clock_Lock(p_owner->p_clock);
+ vlc_clock_Start(p_owner->p_clock, vlc_tick_now(), date);
+ vlc_clock_Unlock(p_owner->p_clock);
+ }
if (p_owner->flushing)
{
@@ -1364,12 +1368,13 @@ static int ModuleThread_PlayVideo( vlc_input_decoder_t *p_owner, picture_t *p_pi
}
else
{
- int ret = DecoderWaitUnblock(p_owner);
+ int ret = DecoderWaitUnblock(p_owner, p_picture->date);
if (ret != VLC_SUCCESS)
{
picture_Release(p_picture);
return ret;
}
+
}
if( unlikely(p_owner->paused) && likely(p_owner->frames_countdown > 0) )
@@ -1506,7 +1511,7 @@ static int ModuleThread_PlayAudio( vlc_input_decoder_t *p_owner, vlc_frame_t *p_
vlc_aout_stream_Flush( p_astream );
}
- int ret = DecoderWaitUnblock(p_owner);
+ int ret = DecoderWaitUnblock(p_owner, p_audio->i_pts);
if (ret != VLC_SUCCESS)
{
block_Release(p_audio);
@@ -1573,7 +1578,7 @@ static void ModuleThread_PlaySpu( vlc_input_decoder_t *p_owner, subpicture_t *p_
}
/* */
- int ret = DecoderWaitUnblock(p_owner);
+ int ret = DecoderWaitUnblock(p_owner, p_subpic->i_start);
if (ret != VLC_SUCCESS || p_subpic->i_start == VLC_TICK_INVALID)
{
@@ -1813,6 +1818,7 @@ static void *DecoderThread( void *p_data )
* is called again. This will avoid a second useless flush (but
* harmless). */
p_owner->flushing = false;
+ p_owner->out_started = false;
p_owner->i_preroll_end = PREROLL_NONE;
continue;
}
@@ -1976,6 +1982,7 @@ CreateDecoder( vlc_object_t *p_parent, const struct vlc_input_decoder_cfg *cfg )
p_owner->b_waiting = false;
p_owner->b_first = true;
p_owner->b_has_data = false;
+ p_owner->out_started = false;
p_owner->error = false;
=====================================
src/input/es_out.c
=====================================
@@ -960,7 +960,10 @@ static void EsOutChangePosition(es_out_sys_t *p_sys, bool b_flush,
vlc_list_foreach(pgrm, &p_sys->programs, node)
{
input_clock_Reset(pgrm->p_input_clock);
+ vlc_clock_main_Lock(pgrm->clocks.main);
pgrm->i_last_pcr = VLC_TICK_INVALID;
+ vlc_clock_Reset(pgrm->clocks.input);
+ vlc_clock_main_Unlock(pgrm->clocks.main);
}
p_sys->b_buffering = true;
@@ -1070,10 +1073,9 @@ static void EsOutDecodersStopBuffering(es_out_sys_t *p_sys, bool b_forced)
EsOutStopFreeVout(p_sys);
/* */
- const vlc_tick_t i_wakeup_delay = VLC_TICK_FROM_MS(10); /* FIXME CLEANUP thread wake up time*/
const vlc_tick_t i_current_date = p_sys->b_paused ? p_sys->i_pause_date : vlc_tick_now();
- const vlc_tick_t update = i_current_date + i_wakeup_delay - i_buffering_duration;
+ const vlc_tick_t update = i_current_date - i_buffering_duration;
/* The call order of these 3 input_clock_t/vlc_clock_main_t functions is
* important:
=====================================
test/Makefile.am
=====================================
@@ -25,6 +25,7 @@ check_PROGRAMS = \
test_libvlc_slaves \
test_src_config_chain \
test_src_clock_clock \
+ test_src_clock_start \
test_src_misc_ancillary \
test_src_misc_variables \
test_src_input_stream \
@@ -166,6 +167,11 @@ test_src_clock_clock_SOURCES = src/clock/clock.c \
../src/clock/clock.c \
../src/clock/clock_internal.c
test_src_clock_clock_LDADD = $(LIBVLCCORE) $(LIBVLC)
+test_src_clock_start_SOURCES = src/clock/clock_start.c \
+ ../src/clock/clock.c \
+ ../src/clock/clock_internal.c
+test_src_clock_start_LDADD = $(LIBVLCCORE) $(LIBVLC)
+
test_src_misc_ancillary_SOURCES = src/misc/ancillary.c
test_src_misc_ancillary_LDADD = $(LIBVLCCORE) $(LIBVLC)
test_src_misc_variables_SOURCES = src/misc/variables.c
=====================================
test/libvlc/test.h
=====================================
@@ -61,7 +61,7 @@ static const char test_default_sample[] = "mock://";
* Some useful common functions
*/
-#define test_log( ... ) printf( "testapi: " __VA_ARGS__ );
+#define test_log( ... ) fprintf(stderr, "testapi: " __VA_ARGS__);
static inline void test_setup(void)
{
=====================================
test/src/clock/clock.c
=====================================
@@ -57,10 +57,11 @@ struct clock_scenario
CLOCK_SCENARIO_RUN,
} type;
+ vlc_tick_t stream_start;
+ vlc_tick_t system_start; /* VLC_TICK_INVALID for vlc_tick_now() */
+
union {
struct {
- vlc_tick_t stream_start;
- vlc_tick_t system_start; /* VLC_TICK_INVALID for vlc_tick_now() */
vlc_tick_t duration;
vlc_tick_t stream_increment; /* VLC_TICK_INVALID for manual increment */
unsigned video_fps;
@@ -84,6 +85,7 @@ struct clock_ctx
vlc_clock_main_t *mainclk;
vlc_clock_t *master;
vlc_clock_t *slave;
+ vlc_clock_t *input;
vlc_tick_t system_start;
vlc_tick_t stream_start;
@@ -328,6 +330,9 @@ static void play_scenario(libvlc_int_t *vlc, struct vlc_tracer *tracer,
assert(mainclk != NULL);
vlc_clock_main_Lock(mainclk);
+ vlc_clock_t *input = vlc_clock_main_CreateInputSlave(mainclk);
+ assert(input != NULL);
+
vlc_clock_t *master = vlc_clock_main_CreateMaster(mainclk, scenario->name,
NULL, NULL);
assert(master != NULL);
@@ -352,10 +357,16 @@ static void play_scenario(libvlc_int_t *vlc, struct vlc_tracer *tracer,
system_start = scenario->system_start;
stream_start = scenario->stream_start;
}
+
+ vlc_clock_Start(input, system_start, stream_start);
+
+ if (scenario->type == CLOCK_SCENARIO_UPDATE)
+ vlc_clock_Start(master, system_start, stream_start);
vlc_clock_main_Unlock(mainclk);
const struct clock_ctx ctx = {
.mainclk = mainclk,
+ .input = input,
.master = master,
.slave = slave,
.scenario = scenario,
@@ -420,6 +431,7 @@ static void play_scenario(libvlc_int_t *vlc, struct vlc_tracer *tracer,
end:
vlc_clock_Delete(master);
vlc_clock_Delete(slave);
+ vlc_clock_Delete(input);
free(slave_name);
vlc_clock_main_Delete(mainclk);
}
@@ -545,8 +557,7 @@ static void normal_check(const struct clock_ctx *ctx, size_t update_count,
{
case TRACER_EVENT_TYPE_UPDATE:
assert(event.update.coeff == 1.0f);
- assert(event.update.offset ==
- scenario->system_start - scenario->stream_start);
+ assert(event.update.offset == 0);
break;
case TRACER_EVENT_TYPE_RENDER_VIDEO:
if (last_video_date != VLC_TICK_INVALID)
@@ -563,6 +574,8 @@ static void normal_check(const struct clock_ctx *ctx, size_t update_count,
vlc_clock_ConvertToSystem(ctx->slave, expected_system_end,
stream_end, 1.0f, NULL);
vlc_clock_Unlock(ctx->slave);
+ fprintf(stderr, "%s:%d: converted=%" PRId64 " == expected_system_end=%" PRId64 "\n",
+ __func__, __LINE__, converted, expected_system_end);
assert(converted == expected_system_end);
}
@@ -678,6 +691,7 @@ static void pause_common(const struct clock_ctx *ctx, vlc_clock_t *updater)
vlc_tick_t system = system_start;
vlc_clock_Lock(updater);
+ vlc_clock_Start(updater, ctx->system_start, ctx->stream_start);
vlc_clock_Update(updater, system, ctx->stream_start, 1.0f);
vlc_clock_Unlock(updater);
@@ -722,7 +736,12 @@ static void convert_paused_common(const struct clock_ctx *ctx, vlc_clock_t *upda
const vlc_tick_t system_start = ctx->system_start;
vlc_tick_t system = system_start;
+ vlc_clock_main_Lock(ctx->mainclk);
+ vlc_clock_main_SetFirstPcr(ctx->mainclk, ctx->system_start, ctx->stream_start);
+ vlc_clock_main_Unlock(ctx->mainclk);
+
vlc_clock_Lock(updater);
+ vlc_clock_Start(updater, ctx->system_start, ctx->stream_start);
vlc_clock_Update(updater, ctx->system_start, ctx->stream_start, 1.0f);
vlc_clock_Unlock(updater);
@@ -731,11 +750,14 @@ static void convert_paused_common(const struct clock_ctx *ctx, vlc_clock_t *upda
vlc_clock_main_Lock(ctx->mainclk);
vlc_clock_main_ChangePause(ctx->mainclk, system, true);
vlc_clock_main_Unlock(ctx->mainclk);
- system += 1;
+ system += 100;
vlc_clock_Lock(ctx->slave);
vlc_tick_t converted = vlc_clock_ConvertToSystem(ctx->slave, system, ctx->stream_start, 1.0f, NULL);
vlc_clock_Unlock(ctx->slave);
+
+ fprintf(stderr, "%s:%d converted=%" PRId64 " == system_start=%" PRId64 "\n",
+ __func__, __LINE__, converted, system_start);
assert(converted == system_start);
}
@@ -764,7 +786,10 @@ static void contexts_run(const struct clock_ctx *ctx)
/* Check that the converted point is valid */
converted = vlc_clock_ConvertToSystem(ctx->slave, system, stream_context0,
1.0f, &clock_id);
+ fprintf(stderr, "%s:%d: clock_id=%" PRIu32 " == 0\n", __func__, __LINE__, clock_id);
assert(clock_id == 0);
+ fprintf(stderr, "%s:%d: converted=%"PRId64 " == system=%" PRId64 "\n",
+ __func__, __LINE__, converted, system);
assert(converted == system);
vlc_clock_Update(ctx->slave, system, stream_context0, 1.0f);
@@ -777,7 +802,9 @@ static void contexts_run(const struct clock_ctx *ctx)
/* Check that we can use the new context (or new origin) */
converted = vlc_clock_ConvertToSystem(ctx->slave, system, stream_context1,
1.0f, &clock_id);
+ fprintf(stderr, "%s:%d: clock_id=%" PRIu32 " == 1\n", __func__, __LINE__, clock_id);
assert(clock_id == 1);
+ fprintf(stderr, "%s:%d: converted=%"PRId64 " == system=%" PRId64 "\n", __func__, __LINE__, converted, system);
assert(converted == system);
/* Check that we can still use the old context when converting a point
@@ -785,7 +812,10 @@ static void contexts_run(const struct clock_ctx *ctx)
converted = vlc_clock_ConvertToSystem(ctx->slave, system,
VLC_TICK_FROM_MS(10) + stream_context0,
1.0f, &clock_id);
+ fprintf(stderr, "%s:%d: clock_id=%" PRIu32 " == 0\n", __func__, __LINE__, clock_id);
assert(clock_id == 0);
+ fprintf(stderr, "%s:%d: converted=%"PRId64 " == system=%" PRId64 "\n",
+ __func__, __LINE__, converted, system_context0 + VLC_TICK_FROM_MS(10));
assert(converted == system_context0 + VLC_TICK_FROM_MS(10));
/* Update on the newest context will cause previous contexts to be removed */
@@ -799,7 +829,10 @@ static void contexts_run(const struct clock_ctx *ctx)
/* Check that we can use the new context (or new origin) */
converted = vlc_clock_ConvertToSystem(ctx->slave, system, stream_context2,
1.0f, &clock_id);
+ fprintf(stderr, "%s:%d: clock_id=%" PRIu32 " == 2\n", __func__, __LINE__, clock_id);
assert(clock_id == 2);
+ fprintf(stderr, "%s:%d: converted=%"PRId64 " == system=%" PRId64 "\n",
+ __func__, __LINE__, converted, system);
assert(converted == system);
/* Update on the newest context will cause previous contexts to be removed */
vlc_clock_Update(ctx->slave, system, stream_context2, 1.0f);
@@ -809,7 +842,10 @@ static void contexts_run(const struct clock_ctx *ctx)
system += VLC_TICK_FROM_MS(100);
converted = vlc_clock_ConvertToSystem(ctx->slave, system, stream_context1, 1.0f,
&clock_id);
+ fprintf(stderr, "%s:%d: clock_id=%" PRIu32 " == 2\n", __func__, __LINE__, clock_id);
assert(clock_id == 2);
+ fprintf(stderr, "%s:%d: converted=%"PRId64 " == system=%" PRId64 "\n",
+ __func__, __LINE__, converted, system_context0);
assert(converted != system_context0);
vlc_clock_main_Unlock(ctx->mainclk);
@@ -894,6 +930,8 @@ static struct clock_scenario clock_scenarios[] = {
.desc = "pause + resume is delaying the next conversion",
.type = CLOCK_SCENARIO_RUN,
.run = master_pause_run,
+ .system_start = VLC_TICK_FROM_MS(100),
+ .stream_start = VLC_TICK_FROM_MS(1000),
},
{
.name = "monotonic_pause",
@@ -901,12 +939,16 @@ static struct clock_scenario clock_scenarios[] = {
.type = CLOCK_SCENARIO_RUN,
.run = monotonic_pause_run,
.disable_jitter = true,
+ .system_start = VLC_TICK_FROM_MS(100),
+ .stream_start = VLC_TICK_FROM_MS(1000),
},
{
.name = "master_convert_paused",
.desc = "it is possible to convert ts while paused",
.type = CLOCK_SCENARIO_RUN,
.run = master_convert_paused_run,
+ .system_start = VLC_TICK_FROM_MS(100),
+ .stream_start = VLC_TICK_FROM_MS(1000),
},
{
.name = "monotonic_convert_paused",
@@ -914,6 +956,8 @@ static struct clock_scenario clock_scenarios[] = {
.type = CLOCK_SCENARIO_RUN,
.run = monotonic_convert_paused_run,
.disable_jitter = true,
+ .system_start = VLC_TICK_FROM_MS(100),
+ .stream_start = VLC_TICK_FROM_MS(1000),
},
{
.name = "contexts",
@@ -921,6 +965,8 @@ static struct clock_scenario clock_scenarios[] = {
.type = CLOCK_SCENARIO_RUN,
.run = contexts_run,
.disable_jitter = true,
+ .system_start = VLC_TICK_FROM_MS(100),
+ .stream_start = VLC_TICK_FROM_MS(1000),
},
};
=====================================
test/src/clock/clock_start.c
=====================================
@@ -0,0 +1,462 @@
+/*****************************************************************************
+ * clock/clock_start.c: test for the vlc clock
+ *****************************************************************************
+ * Copyright (C) 2024 Videolabs
+ *
+ * Authors: Alexandre Janniaux <ajanni at videolabs.io>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_tick.h>
+#include <vlc_es.h>
+#include <vlc_tracer.h>
+
+#include "../../../src/clock/clock.h"
+
+#include <vlc/vlc.h>
+#include "../../libvlc/test.h"
+#include "../../../lib/libvlc_internal.h"
+
+#define MODULE_NAME test_clock_clock
+#undef VLC_DYNAMIC_PLUGIN
+
+#include <vlc_plugin.h>
+#include <vlc_vector.h>
+
+/* Define a builtin module for mocked parts */
+const char vlc_module_name[] = MODULE_STRING;
+
+/**
+ * Fixture structure containing the clocks used for each test.
+ */
+struct clock_fixture_simple {
+ vlc_clock_main_t *main;
+ vlc_clock_t *input;
+ vlc_clock_t *master;
+ vlc_clock_t *slave;
+};
+
+struct clock_point
+{
+ vlc_tick_t system;
+ vlc_tick_t stream;
+};
+
+/**
+ * Initialize the fixture.
+ *
+ * It must be released using \ref destroy_fixture_simple.
+ */
+static struct clock_fixture_simple
+init_fixture_simple(struct vlc_logger *logger,
+ bool is_input_master,
+ vlc_tick_t input_dejitter,
+ vlc_tick_t output_dejitter)
+{
+ struct clock_fixture_simple f = {
+ .main = vlc_clock_main_New(logger, NULL),
+ };
+ assert(f.main != NULL);
+
+ vlc_clock_main_Lock(f.main);
+ vlc_clock_main_SetInputDejitter(f.main, input_dejitter);
+ vlc_clock_main_SetDejitter(f.main, output_dejitter);
+
+ if (is_input_master)
+ {
+ f.input = vlc_clock_main_CreateInputMaster(f.main);
+ f.master = f.input;
+ }
+ else
+ {
+ f.input = vlc_clock_main_CreateInputSlave(f.main);
+ f.master = vlc_clock_main_CreateMaster(f.main, "Driving", NULL, NULL);
+ }
+ assert(f.input != NULL);
+ assert(f.master != NULL);
+
+ f.slave = vlc_clock_main_CreateSlave(f.main, "Output", AUDIO_ES,
+ NULL, NULL);
+ assert(f.slave != NULL);
+ vlc_clock_main_Unlock(f.main);
+
+ return f;
+}
+
+static void destroy_fixture_simple(struct clock_fixture_simple *f)
+{
+ vlc_clock_Delete(f->slave);
+ if (f->master != f->input)
+ vlc_clock_Delete(f->master);
+ vlc_clock_Delete(f->input);
+ vlc_clock_main_Delete(f->main);
+}
+
+/**
+ * Check that conversion doesn't work as long as clock has not
+ * been started.
+ **/
+#if 0
+static void test_clock_without_start(struct vlc_logger *logger)
+{
+ struct clock_fixture_simple f = init_fixture_simple(logger, true, 0, 0);
+
+ vlc_clock_main_Lock(f.main);
+ const vlc_tick_t now = VLC_TICK_FROM_MS(1000);
+ {
+ vlc_clock_Update(f.master, now, now * 1000, 1.);
+ vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL);
+ //assert(result == VLC_TICK_INVALID);
+ }
+
+ vlc_clock_Start(f.slave, now, now * 1000);
+
+ {
+ vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL);
+ assert(result == VLC_TICK_INVALID);
+ }
+ vlc_clock_main_Unlock(f.main);
+
+ destroy_fixture_simple(&f);
+}
+#endif
+
+/**
+ * Check that conversion works and is valid after the clock start.
+ **/
+static void test_clock_with_start(struct vlc_logger *logger)
+{
+ fprintf(stderr, "%s:\n", __func__);
+ struct clock_fixture_simple f = init_fixture_simple(logger, true, 0, 0);
+ const vlc_tick_t now = VLC_TICK_FROM_MS(1000);
+
+ vlc_clock_main_Lock(f.main);
+ vlc_clock_Update(f.master, now, now * 1000, 1.);
+ vlc_clock_Start(f.input, now, now * 1000);
+ vlc_clock_Start(f.slave, now, now * 1000);
+
+ vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL);
+ assert(result != VLC_TICK_INVALID);
+ assert(result == now);
+ vlc_clock_main_Unlock(f.main);
+
+ destroy_fixture_simple(&f);
+}
+
+/**
+ * Check that master clock can start.
+ **/
+static void test_clock_with_start_master(struct vlc_logger *logger)
+{
+ fprintf(stderr, "%s:\n", __func__);
+ struct clock_fixture_simple f = init_fixture_simple(logger, false, 0, 0);
+ const vlc_tick_t now = VLC_TICK_FROM_MS(1000);
+
+ vlc_clock_main_Lock(f.main);
+ vlc_clock_Start(f.input, now, now * 1000);
+ vlc_clock_Start(f.master, now, now * 1000);
+ vlc_clock_Start(f.slave, now, now * 1000);
+ vlc_clock_Update(f.master, now, now * 1000, 1.);
+
+ vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL);
+ assert(result != VLC_TICK_INVALID);
+ assert(result == now);
+ vlc_clock_main_Unlock(f.main);
+
+ destroy_fixture_simple(&f);
+}
+
+/**
+ * Check that conversion works and is valid after the clock start,
+ * when start is done later.
+ **/
+static void test_clock_with_output_dejitter(struct vlc_logger *logger)
+{
+ fprintf(stderr, "%s:\n", __func__);
+ struct clock_fixture_simple f = init_fixture_simple(logger, true, 0, 0);
+ vlc_clock_main_Lock(f.main);
+
+ const vlc_tick_t now = VLC_TICK_FROM_MS(1000);
+ vlc_clock_Update(f.master, now, now * 1000, 1.);
+ vlc_clock_Start(f.input, 2 * now, now * 1000);
+ vlc_clock_Start(f.slave, 2 * now, now * 1000);
+
+ vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL);
+ fprintf(stderr, "%s:%d: now=%" PRId64 " converted=%" PRId64 "\n",
+ __func__, __LINE__, now, result);
+ assert(result != VLC_TICK_INVALID);
+ assert(result == 2 * now);
+ vlc_clock_main_Unlock(f.main);
+
+ destroy_fixture_simple(&f);
+}
+
+/**
+ * Check that clock start is working correctly with an output driving
+ * the clock bus.
+ **/
+static void test_clock_with_output_clock_start(struct vlc_logger *logger)
+{
+ fprintf(stderr, "%s:\n", __func__);
+ struct clock_fixture_simple f = init_fixture_simple(logger, false, 0, 0);
+ vlc_clock_main_Lock(f.main);
+
+ /* When the input is not driving the clock bus, we should be able to start
+ * without any reference point since the output will provide it later. */
+ const vlc_tick_t now = VLC_TICK_FROM_MS(1000);
+ vlc_clock_Start(f.input, now, now * 1000);
+ vlc_clock_Start(f.slave, now, now * 1000);
+
+ /* We provide the clock point to the clock bus. The system time should
+ * approximately match the start date. */
+ vlc_clock_Update(f.master, now, now * 1000, 1.);
+
+ vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL);
+ assert(result != VLC_TICK_INVALID);
+ assert(result == now);
+ vlc_clock_main_Unlock(f.main);
+
+ destroy_fixture_simple(&f);
+}
+
+/**
+ * Check that monotonic clock start is working correctly.
+ **/
+static void test_clock_with_monotonic_clock(struct vlc_logger *logger)
+{
+ fprintf(stderr, "%s:\n", __func__);
+ struct clock_fixture_simple f = init_fixture_simple(logger, false, 0, 0);
+ vlc_clock_main_Lock(f.main);
+
+ /* When the input is not driving the clock bus, we should be able to start
+ * without any reference point since the output will provide it later. */
+ const vlc_tick_t now = VLC_TICK_FROM_MS(1000);
+ vlc_clock_Start(f.input, now, now * 1000);
+
+ /* The output wants to start before a reference point is found. */
+ vlc_clock_Start(f.slave, now, now * 1000);
+
+ // TODO
+ vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, now, now * 1000, 1., NULL);
+ assert(result != VLC_TICK_INVALID);
+ assert(result == now);
+ vlc_clock_main_Unlock(f.main);
+
+
+ destroy_fixture_simple(&f);
+}
+
+/**
+ * Check that monotonic clock start accounts for the start date.
+ **/
+static vlc_tick_t test_clock_with_monotonic_clock_start(
+ struct vlc_logger *logger,
+ vlc_tick_t input_dejitter,
+ vlc_tick_t output_dejitter,
+ struct clock_point start_point,
+ struct clock_point output_point,
+ struct clock_point convert_point
+){
+ fprintf(stderr, "%s:\n", __func__);
+ struct clock_fixture_simple f = init_fixture_simple(logger, false, input_dejitter, output_dejitter);
+ vlc_clock_main_Lock(f.main);
+
+ /* When the input is not driving the clock bus, we should be able to start
+ * without any reference point since the output will provide it later. */
+ vlc_clock_Start(f.input, start_point.system, start_point.stream);
+
+ /* The output wants to start before a reference point is found. */
+ vlc_clock_Start(f.slave, output_point.system, output_point.stream);
+
+ vlc_tick_t result = vlc_clock_ConvertToSystem(f.slave, convert_point.system, convert_point.stream, 1., NULL);
+ vlc_clock_main_Unlock(f.main);
+
+ destroy_fixture_simple(&f);
+ return result;
+}
+
+static vlc_tick_t test_clock_slave_update(
+ struct vlc_logger *logger,
+ struct clock_point start_point,
+ struct clock_point output_point
+){
+ fprintf(stderr, "%s:\n", __func__);
+ struct clock_fixture_simple f = init_fixture_simple(logger, false, 0, 0);
+ vlc_clock_main_Lock(f.main);
+
+ /* When the input is not driving the clock bus, we should be able to start
+ * without any reference point since the output will provide it later. */
+ vlc_clock_Start(f.input, start_point.system, start_point.stream);
+ vlc_clock_Update(f.master, start_point.system, start_point.stream, 1.);
+
+ /* The output wants to start before a reference point is found. */
+ vlc_clock_Start(f.slave, output_point.system, output_point.stream);
+
+ vlc_tick_t result = vlc_clock_Update(f.slave, output_point.system, output_point.stream, 1.);
+ vlc_clock_main_Unlock(f.main);
+
+ destroy_fixture_simple(&f);
+ return result;
+}
+
+static void test_clock_start_reset(
+ struct vlc_logger *logger
+){
+ fprintf(stderr, "%s:\n", __func__);
+ struct clock_fixture_simple f = init_fixture_simple(logger, false, 0, 0);
+ vlc_clock_main_Lock(f.main);
+
+ const vlc_tick_t now = VLC_TICK_FROM_MS(1000);
+ struct clock_point start_point;
+ start_point = (struct clock_point){ now, now * 1000 };
+
+ /* When the input is not driving the clock bus, we should be able to start
+ * without any reference point since the output will provide it later. */
+ vlc_clock_Start(f.input, start_point.system, start_point.stream);
+ vlc_clock_Update(f.master, start_point.system, start_point.stream, 1.);
+ vlc_clock_main_Reset(f.main);
+
+ vlc_tick_t result;
+ result = vlc_clock_ConvertToSystem(f.slave, start_point.system, start_point.stream, 1., NULL);
+ //assert(result == VLC_TICK_INVALID);
+
+ start_point.system *= 2;
+ vlc_clock_Start(f.input, start_point.system, start_point.stream);
+ vlc_clock_Update(f.master, start_point.system, start_point.stream, 1.);
+ result = vlc_clock_ConvertToSystem(f.slave, start_point.system, start_point.stream, 1., NULL);
+ assert(result == start_point.system);
+ vlc_clock_main_Unlock(f.main);
+
+ destroy_fixture_simple(&f);
+}
+
+static void test_clock_slave_only(
+ struct vlc_logger *logger
+){
+ fprintf(stderr, "%s:\n", __func__);
+ struct clock_fixture_simple f = init_fixture_simple(logger, false, 0, 0);
+ vlc_clock_main_Lock(f.main);
+
+ const vlc_tick_t now = VLC_TICK_FROM_MS(1000);
+ struct clock_point start_point = { now, now * 1000 };
+
+ /* When the input is not driving the clock bus, we should be able to start
+ * without any reference point since the output will provide it later. */
+ vlc_clock_Start(f.input, start_point.system, start_point.stream);
+
+ vlc_clock_Start(f.slave, start_point.system, start_point.stream);
+ vlc_tick_t result;
+ result = vlc_clock_ConvertToSystem(f.slave, start_point.system, start_point.stream, 1., NULL);
+ assert(result == start_point.system);
+
+ result = vlc_clock_ConvertToSystem(f.slave, start_point.system, start_point.stream + 1000, 1., NULL);
+ assert(result == start_point.system + 1000);
+ vlc_clock_main_Unlock(f.main);
+
+ destroy_fixture_simple(&f);
+}
+int main(void)
+{
+ libvlc_instance_t *libvlc = libvlc_new(0, NULL);
+ assert(libvlc != NULL);
+
+ libvlc_int_t *vlc = libvlc->p_libvlc_int;
+ struct vlc_logger *logger = vlc->obj.logger;
+
+ const vlc_tick_t now = VLC_TICK_FROM_MS(1000);
+
+ //test_clock_without_start(logger);
+ test_clock_with_start(logger);
+ test_clock_with_start_master(logger);
+ test_clock_with_output_dejitter(logger);
+ test_clock_with_output_clock_start(logger);
+ test_clock_with_monotonic_clock(logger);
+
+ struct clock_point start_point, output_point;
+ start_point = (struct clock_point){ now, now * 1000 };
+ output_point = (struct clock_point){ now, now * 1000 };
+
+ vlc_tick_t result = test_clock_with_monotonic_clock_start(
+ logger, 0, 0, start_point, start_point, start_point);
+ fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, start_point.system);
+ assert(result == start_point.system);
+
+ /* Monotonic clock must start at the start point. */
+ start_point = (struct clock_point){ 2 * now, now * 1000 };
+ result = test_clock_with_monotonic_clock_start(
+ logger, 0, 0, start_point, output_point, output_point);
+ fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, start_point.system);
+ assert(result == start_point.system);
+
+ /* A point later in the future will be started later also. */
+ start_point = (struct clock_point){ now, now * 1000 };
+ output_point = (struct clock_point){ now, now * 1000 + 100};
+ result = test_clock_with_monotonic_clock_start(
+ logger, 0, 0, start_point, output_point, output_point);
+ fprintf(stderr, "result(%" PRId64 ") = start_point.system(%" PRId64 ")\n", result, start_point.system + 100);
+ assert(result == start_point.system + 100);
+
+ /* Input dejitter and PCR delay are accounted together. */
+ result = test_clock_with_monotonic_clock_start(
+ logger, 30, 0, start_point, output_point, output_point);
+ fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, start_point.system + 130);
+ assert(result == start_point.system + 130);
+
+ /* Output dejitter will not account the current PCR delay. */
+ result = test_clock_with_monotonic_clock_start(
+ logger, 0, 100, start_point, output_point, output_point);
+ fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, start_point.system + 100);
+ assert(result == start_point.system + 100);
+
+ /* The highest dejitter value is used. */
+ result = test_clock_with_monotonic_clock_start(
+ logger, 33, 55, start_point, output_point, output_point);
+ fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, start_point.system + 133);
+ assert(result == start_point.system + 133);
+
+ /* The highest dejitter value is used. */
+ result = test_clock_with_monotonic_clock_start(
+ logger, 33, 555, start_point, output_point, output_point);
+ fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, start_point.system + 555);
+ assert(result == start_point.system + 555);
+
+ start_point = (struct clock_point){ now, now * 1000 };
+ output_point = (struct clock_point){ now, now * 1000 };
+ result = test_clock_slave_update(logger, start_point, output_point);
+ fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, (vlc_tick_t)0);
+ assert(result == 0);
+
+ output_point = (struct clock_point){ now + 10, now * 1000 };
+ result = test_clock_slave_update(logger, start_point, output_point);
+ fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, (vlc_tick_t)-10);
+ assert(result == -10);
+
+ output_point = (struct clock_point){ now, now * 1000 + 10 };
+ result = test_clock_slave_update(logger, start_point, output_point);
+ fprintf(stderr, "%" PRId64 " = %" PRId64 "\n", result, (vlc_tick_t)10);
+ assert(result == 10);
+
+ test_clock_start_reset(logger);
+ test_clock_slave_only(logger);
+
+ libvlc_release(libvlc);
+}
+
=====================================
test/src/meson.build
=====================================
@@ -35,6 +35,16 @@ vlc_tests += {
'link_with' : [libvlc, libvlccore],
}
+vlc_tests += {
+ 'name' : 'test_src_clock_start',
+ 'sources' : files(
+ 'clock/clock_start.c',
+ '../../src/clock/clock.c',
+ '../../src/clock/clock_internal.c'),
+ 'suite' : ['src', 'test_src'],
+ 'link_with' : [libvlc, libvlccore],
+}
+
vlc_tests += {
'name' : 'test_src_misc_variables',
'sources' : files('misc/variables.c'),
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/33e05c8474f431fe735826961e89b38b9f4e3664...ae286927a6eab3436fd5110f1f51ee4c15704bcf
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/33e05c8474f431fe735826961e89b38b9f4e3664...ae286927a6eab3436fd5110f1f51ee4c15704bcf
You're receiving this email because of your account on code.videolan.org.
VideoLAN code repository instance
More information about the vlc-commits
mailing list