[vlc-commits] [Git][videolan/vlc][master] 22 commits: clock: move variables used to convert in a new struct

Steve Lhomme (@robUx4) gitlab at videolan.org
Sat Jul 13 11:57:54 UTC 2024



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
34d32f14 by Thomas Guillem at 2024-07-13T11:29:36+00:00
clock: move variables used to convert in a new struct

- - - - -
32cadbfc by Thomas Guillem at 2024-07-13T11:29:36+00:00
clock: early return

- - - - -
47bde44e by Thomas Guillem at 2024-07-13T11:29:36+00:00
clock: add vlc_clock_main_CreateInputSlave

- - - - -
0b21de86 by Thomas Guillem at 2024-07-13T11:29:36+00:00
es_out: use vlc_clock_main_CreateInputSlave()

- - - - -
c18be747 by Thomas Guillem at 2024-07-13T11:29:36+00:00
clock: reset the clock on SetFirstPCR()

- - - - -
cf93349b by Thomas Guillem at 2024-07-13T11:29:36+00:00
input_clock: add discontinuity to the update callback

- - - - -
36713cdc by Thomas Guillem at 2024-07-13T11:29:36+00:00
clock: create a new context when SetFirstPcr() is called

The previous context will be used by other output clocks in read-only
until all outputs switch to the newly created context (when all outputs
do update using the clock_id of the new context).

The previous contexts are removed when not used anymore.

- - - - -
4ea725d7 by Thomas Guillem at 2024-07-13T11:29:36+00:00
es_out: handle input clock discontinuity

Call vlc_clock_main_SetFirstPcr() in case of PCR discontinuity.

- - - - -
bf5dd04b by Thomas Guillem at 2024-07-13T11:29:36+00:00
clock: expose the clock context used for conversion

Increasing uint32_t, that will be used by output to identify when a
converted PTS discontinued with the previous one.

- - - - -
d253bec5 by Thomas Guillem at 2024-07-13T11:29:36+00:00
vout: always convert the just decoded picture

- - - - -
60c70e2d by Thomas Guillem at 2024-07-13T11:29:36+00:00
vout: merge if conditions

- - - - -
34b1c4a2 by Thomas Guillem at 2024-07-13T11:29:36+00:00
vout: fix invalid PTS when switching clock contexts

When using a deinterlace module (from the static chain).

- - - - -
183bc17c by Thomas Guillem at 2024-07-13T11:29:36+00:00
aout: trigger a discontinuity when switching clock contexts

- - - - -
156fa18c by Thomas Guillem at 2024-07-13T11:29:36+00:00
test: player: rework ctx_flags

Move them up, and use a define instead.

- - - - -
de054823 by Thomas Guillem at 2024-07-13T11:29:36+00:00
test: player: store creation flags in ctx

- - - - -
37062157 by Thomas Guillem at 2024-07-13T11:29:36+00:00
test: player: add discontinuities option

- - - - -
d3ee6c48 by Thomas Guillem at 2024-07-13T11:29:36+00:00
test: player: forward ctx to submodules

- - - - -
028c6847 by Thomas Guillem at 2024-07-13T11:29:36+00:00
test: player: add AUDIO_INSTANT_DRAIN flag

- - - - -
a769f57a by Thomas Guillem at 2024-07-13T11:29:36+00:00
test: player: rename REPORT_LIST

- - - - -
1f7bac2b by Thomas Guillem at 2024-07-13T11:29:36+00:00
test: player: report aout first pts

- - - - -
b2a86a28 by Thomas Guillem at 2024-07-13T11:29:36+00:00
test: player: add test_clock_discontinuities

Check that the audio output receive all discontinuities triggered by
clock contexts switch.

- - - - -
f8b6c4e4 by Thomas Guillem at 2024-07-13T11:29:36+00:00
test: clock: test contexts

Check the clock API when using several clock contexts.

- - - - -


11 changed files:

- src/audio_output/dec.c
- src/clock/clock.c
- src/clock/clock.h
- src/clock/input_clock.c
- src/clock/input_clock.h
- src/input/decoder.c
- src/input/es_out.c
- src/video_output/video_output.c
- src/video_output/vout_subpictures.c
- test/src/clock/clock.c
- test/src/player/player.c


Changes:

=====================================
src/audio_output/dec.c
=====================================
@@ -39,6 +39,8 @@
 #include "clock/clock.h"
 #include "libvlc.h"
 
+#define BLOCK_FLAG_CORE_PRIVATE_FILTERED (1 << BLOCK_FLAG_CORE_PRIVATE_SHIFT)
+
 struct vlc_aout_stream
 {
     aout_instance_t *instance;
@@ -59,6 +61,7 @@ struct vlc_aout_stream
     struct
     {
         struct vlc_clock_t *clock;
+        uint32_t clock_id;
         float rate; /**< Play-out speed rate */
         vlc_tick_t resamp_start_drift; /**< Resampler drift absolute value */
         int resamp_type; /**< Resampler mode (FIXME: redundant / resampling) */
@@ -367,6 +370,7 @@ vlc_aout_stream * vlc_aout_stream_New(audio_output_t *p_aout,
     stream->filter_format = stream->mixer_format = stream->input_format = *p_format;
 
     stream->sync.clock = cfg->clock;
+    stream->sync.clock_id = 0;
     stream->str_id = cfg->str_id;
 
     stream->timing.rate_audio_ts = VLC_TICK_INVALID;
@@ -570,7 +574,7 @@ static void stream_Silence (vlc_aout_stream *stream, vlc_tick_t length, vlc_tick
     vlc_clock_Lock(stream->sync.clock);
     const vlc_tick_t system_pts =
        vlc_clock_ConvertToSystem(stream->sync.clock, system_now, pts,
-                                 stream->sync.rate);
+                                 stream->sync.rate, NULL);
     vlc_clock_Unlock(stream->sync.clock);
     stream->timing.played_samples += block->i_nb_samples;
     aout->play(aout, block, system_pts);
@@ -844,7 +848,7 @@ int vlc_aout_stream_Play(vlc_aout_stream *stream, block_t *block)
     if (unlikely(ret == AOUT_DEC_FAILED))
         goto drop; /* Pipeline is unrecoverably broken :-( */
 
-    if (stream->filters)
+    if (stream->filters && (block->i_flags & BLOCK_FLAG_CORE_PRIVATE_FILTERED) == 0)
     {
         if (atomic_load_explicit(&owner->vp.update, memory_order_relaxed))
         {
@@ -879,11 +883,20 @@ int vlc_aout_stream_Play(vlc_aout_stream *stream, block_t *block)
     /* Drift correction */
     vlc_tick_t system_now = vlc_tick_now();
 
+    uint32_t clock_id;
     vlc_clock_Lock(stream->sync.clock);
     vlc_tick_t play_date =
         vlc_clock_ConvertToSystem(stream->sync.clock, system_now, block->i_pts,
-                                  stream->sync.rate);
+                                  stream->sync.rate, &clock_id);
     vlc_clock_Unlock(stream->sync.clock);
+
+    if (clock_id != stream->sync.clock_id)
+    {
+        stream->sync.clock_id = clock_id;
+        block->i_flags |= BLOCK_FLAG_CORE_PRIVATE_FILTERED;
+        return stream_StartDiscontinuity(stream, block);
+    }
+
     stream_Synchronize(stream, system_now, play_date, block->i_pts);
 
     vlc_audio_meter_Process(&owner->meter, block, play_date);
@@ -952,7 +965,7 @@ void vlc_aout_stream_ChangePause(vlc_aout_stream *stream, bool paused, vlc_tick_
             vlc_tick_t play_date =
                 vlc_clock_ConvertToSystem(stream->sync.clock, date,
                                           stream->timing.rate_audio_ts,
-                                          stream->sync.rate);
+                                          stream->sync.rate, NULL);
             vlc_clock_Unlock(stream->sync.clock);
             stream->timing.rate_system_ts = play_date;
         }


=====================================
src/clock/clock.c
=====================================
@@ -45,6 +45,20 @@ struct vlc_clock_listener_id
     void *data;
 };
 
+struct vlc_clock_context
+{
+    double rate;
+    double coeff;
+    vlc_tick_t offset;
+    uint32_t clock_id;
+
+    clock_point_t last;
+    clock_point_t wait_sync_ref;
+
+    struct vlc_list node;
+    struct vlc_list using_clocks;
+};
+
 struct vlc_clock_main_t
 {
     struct vlc_logger *logger;
@@ -61,33 +75,30 @@ struct vlc_clock_main_t
      * Linear function
      * system = ts * coeff / rate + offset
      */
-    clock_point_t last;
     average_t coeff_avg; /* Moving average to smooth out the instant coeff */
-    double rate;
-    double coeff;
-    vlc_tick_t offset;
     vlc_tick_t delay;
+    struct vlc_clock_context *context;
 
     vlc_tick_t pause_date;
 
     unsigned wait_sync_ref_priority;
-    clock_point_t wait_sync_ref; /* When the master */
     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 */
 
     struct VLC_VECTOR(vlc_clock_listener_id *) listeners;
+    struct vlc_list prev_contexts;
 };
 
 struct vlc_clock_ops
 {
-    vlc_tick_t (*update)(vlc_clock_t *clock, vlc_tick_t system_now,
-                         vlc_tick_t ts, double rate,
+    vlc_tick_t (*update)(vlc_clock_t *clock, struct vlc_clock_context *ctx,
+                         vlc_tick_t system_now, vlc_tick_t ts, double rate,
                          unsigned frame_rate, unsigned frame_rate_base);
     void (*reset)(vlc_clock_t *clock);
     vlc_tick_t (*set_delay)(vlc_clock_t *clock, vlc_tick_t delay);
-    vlc_tick_t (*to_system)(vlc_clock_t *clock, vlc_tick_t system_now,
-                            vlc_tick_t ts, double rate);
+    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);
 };
 
 struct vlc_clock_t
@@ -100,6 +111,12 @@ struct vlc_clock_t
 
     const struct vlc_clock_cbs *cbs;
     void *cbs_data;
+
+    struct vlc_list node;
+
+    struct vlc_clock_context *context;
+
+    vlc_tick_t last_conversion;
 };
 
 vlc_clock_listener_id *
@@ -181,25 +198,156 @@ static inline void TraceRender(struct vlc_tracer *tracer, const char *type,
                                  VLC_TRACE_END);
 }
 
-static vlc_tick_t main_stream_to_system(vlc_clock_main_t *main_clock,
-                                        vlc_tick_t ts)
+static void context_reset(struct vlc_clock_context *ctx)
+{
+    ctx->coeff = 1.0f;
+    ctx->rate = 1.0f;
+    ctx->offset = VLC_TICK_INVALID;
+    ctx->wait_sync_ref = clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
+    ctx->last = clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
+}
+
+static void context_init(struct vlc_clock_context *ctx)
+{
+    vlc_list_init(&ctx->using_clocks);
+    context_reset(ctx);
+    ctx->clock_id = 0;
+}
+
+static struct vlc_clock_context *context_new(void)
 {
-    if (main_clock->last.system == VLC_TICK_INVALID)
+    struct vlc_clock_context *ctx = malloc(sizeof(*ctx));
+    if (unlikely(ctx == NULL))
+        return NULL;
+    context_init(ctx);
+    return ctx;
+}
+
+static vlc_tick_t context_stream_to_system(const struct vlc_clock_context *ctx,
+                                           vlc_tick_t ts)
+{
+    if (ctx->last.system == VLC_TICK_INVALID)
         return VLC_TICK_INVALID;
-    return ((vlc_tick_t) (ts * main_clock->coeff / main_clock->rate))
-            + main_clock->offset;
+    return ((vlc_tick_t) (ts * ctx->coeff / ctx->rate)) + ctx->offset;
+}
+
+static void
+vlc_clock_remove_current_context(vlc_clock_t *clock)
+{
+    vlc_clock_main_t *main_clock = clock->owner;
+    struct vlc_clock_context *ctx = clock->context;
+    assert(ctx != NULL);
+
+    /* Remove the clock from the context using_clocks */
+    vlc_list_remove(&clock->node);
+    uint32_t clock_id = ctx->clock_id;
+    bool removed = false;
+
+    /* Free the context if it is not used anymore. Don't free the main context
+     * that should stay alive even when no clocks are referencing it.  */
+    if (vlc_list_is_empty(&ctx->using_clocks) && ctx != main_clock->context)
+    {
+        /* Remove the context from the list of previous contexts */
+        vlc_list_remove(&ctx->node);
+        free(ctx);
+        removed = true;
+    }
+    clock->context = NULL;
+
+    if (main_clock->logger != NULL && clock->track_str_id != NULL
+     && ctx != main_clock->context)
+        vlc_warning(main_clock->logger, "clock(%s): unused clock context(%u)%s",
+                    clock->track_str_id, clock_id, removed ? " (removed)" : "");
+}
+
+static void
+vlc_clock_attach_context(vlc_clock_t *clock, struct vlc_clock_context *ctx)
+{
+    vlc_list_append(&clock->node, &ctx->using_clocks);
+    clock->context = ctx;
+}
+
+static void
+vlc_clock_switch_context(vlc_clock_t *clock, struct vlc_clock_context *ctx)
+{
+    vlc_clock_main_t *main_clock = clock->owner;
+
+    vlc_clock_remove_current_context(clock);
+    vlc_clock_attach_context(clock, ctx);
+
+    if (main_clock->logger != NULL && clock->track_str_id != NULL)
+        vlc_warning(main_clock->logger, "clock(%s): using clock context(%u)",
+                    clock->track_str_id, ctx->clock_id);
+}
+
+static void
+context_get_closest(vlc_clock_t *clock, struct vlc_clock_context *ctx,
+                    vlc_tick_t system_now, vlc_tick_t ts,
+                    struct vlc_clock_context **closest_ctx,
+                    vlc_tick_t *closest_diff)
+{
+    /* Find the context which has the smallest gap with the last conversion. */
+    vlc_tick_t converted =
+        clock->ops->to_system(clock, ctx, system_now, ts, 1.0);
+
+    vlc_tick_t diff = converted - clock->last_conversion;
+
+    if (diff < 0)
+        diff = -diff;
+
+    if (diff < *closest_diff)
+    {
+        *closest_diff = diff;
+        *closest_ctx = ctx;
+    }
+}
+
+static struct vlc_clock_context *
+vlc_clock_get_context(vlc_clock_t *clock, vlc_tick_t system_now, vlc_tick_t ts,
+                      bool update)
+{
+    vlc_clock_main_t *main_clock = clock->owner;
+
+    /* The input clock is always using the last context */
+    if (clock->context == NULL)
+        return main_clock->context;
+
+    if (vlc_list_is_empty(&main_clock->prev_contexts)
+     || clock->context == main_clock->context)
+        return main_clock->context;
+
+    vlc_tick_t closest_diff = VLC_TICK_MAX;
+    struct vlc_clock_context *ctx = NULL;
+
+    /* Iterate through newer contexts, and not past ones */
+    for (struct vlc_clock_context *ctx_it = clock->context; ctx_it != NULL;
+         ctx_it = vlc_list_next_entry_or_null(&main_clock->prev_contexts, ctx_it,
+                                              struct vlc_clock_context, node))
+        context_get_closest(clock, ctx_it, system_now, ts, &ctx, &closest_diff);
+
+    context_get_closest(clock, main_clock->context, system_now, ts, &ctx,
+                        &closest_diff);
+
+    if (clock->context != ctx && update)
+    {
+        /* Switch the clock context to the newer one. This means that contexts
+         * past the new one will never be selected again by this current clock.
+         * Switch context only when updating, since outputs will hold frames
+         * until there are displayed. */
+        vlc_clock_switch_context(clock, ctx);
+    }
+
+    return ctx;
 }
 
 static void vlc_clock_main_reset(vlc_clock_main_t *main_clock)
 {
-    main_clock->coeff = 1.0f;
-    main_clock->rate = 1.0f;
-    AvgResetAndFill(&main_clock->coeff_avg, main_clock->coeff);
-    main_clock->offset = VLC_TICK_INVALID;
+    struct vlc_clock_context *ctx = main_clock->context;
+
+    context_reset(ctx);
+    AvgResetAndFill(&main_clock->coeff_avg, ctx->coeff);
 
     main_clock->wait_sync_ref_priority = UINT_MAX;
-    main_clock->wait_sync_ref =
-        main_clock->last = clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
     vlc_cond_broadcast(&main_clock->cond);
 }
 
@@ -224,19 +372,20 @@ static inline void vlc_clock_on_update(vlc_clock_t *clock,
 
 
 static void vlc_clock_master_update_coeff(
-    vlc_clock_t *clock, vlc_tick_t system_now, vlc_tick_t ts, double rate)
+    vlc_clock_t *clock, struct vlc_clock_context *ctx,
+    vlc_tick_t system_now, vlc_tick_t ts, double rate)
 {
     vlc_clock_main_t *main_clock = clock->owner;
     vlc_mutex_assert(&main_clock->lock);
 
-    if (main_clock->last.system != VLC_TICK_INVALID
-     && ts != main_clock->last.stream)
+    if (ctx->last.system != VLC_TICK_INVALID
+     && ts != ctx->last.stream)
     {
-        if (rate == main_clock->rate)
+        if (rate == ctx->rate)
         {
             /* We have a reference so we can update coeff */
-            vlc_tick_t system_diff = system_now - main_clock->last.system;
-            vlc_tick_t stream_diff = ts - main_clock->last.stream;
+            vlc_tick_t system_diff = system_now - ctx->last.system;
+            vlc_tick_t stream_diff = ts - ctx->last.stream;
 
             double instant_coeff = system_diff / (double) stream_diff * rate;
 
@@ -273,35 +422,35 @@ static void vlc_clock_master_update_coeff(
             else
             {
                 AvgUpdate(&main_clock->coeff_avg, instant_coeff);
-                main_clock->coeff = AvgGet(&main_clock->coeff_avg);
+                ctx->coeff = AvgGet(&main_clock->coeff_avg);
             }
         }
     }
     else
     {
         main_clock->wait_sync_ref_priority = UINT_MAX;
-        main_clock->wait_sync_ref =
+        ctx->wait_sync_ref =
             clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
 
         vlc_clock_SendEvent(main_clock, discontinuity);
     }
 
-    main_clock->offset =
-        system_now - ((vlc_tick_t) (ts * main_clock->coeff / rate));
+    ctx->offset = system_now - ((vlc_tick_t) (ts * ctx->coeff / rate));
 
     if (main_clock->tracer != NULL && clock->track_str_id != NULL)
         vlc_tracer_Trace(main_clock->tracer,
                          VLC_TRACE("type", "RENDER"),
                          VLC_TRACE("id", clock->track_str_id),
-                         VLC_TRACE_TICK_NS("offset", main_clock->offset),
-                         VLC_TRACE("coeff", main_clock->coeff),
+                         VLC_TRACE_TICK_NS("offset", ctx->offset),
+                         VLC_TRACE("coeff", ctx->coeff),
                          VLC_TRACE_END);
 
-    main_clock->rate = rate;
+    ctx->rate = rate;
     vlc_cond_broadcast(&main_clock->cond);
 }
 
 static vlc_tick_t vlc_clock_master_update(vlc_clock_t *clock,
+                                          struct vlc_clock_context *ctx,
                                           vlc_tick_t system_now,
                                           vlc_tick_t ts, double rate,
                                           unsigned frame_rate,
@@ -312,12 +461,15 @@ static vlc_tick_t vlc_clock_master_update(vlc_clock_t *clock,
     if (unlikely(ts == VLC_TICK_INVALID || system_now == VLC_TICK_INVALID))
         return VLC_TICK_INVALID;
 
+    if (ctx != main_clock->context)
+        return VLC_TICK_INVALID; /* Don't update previous contexts */
+
     /* If system_now is VLC_TICK_MAX, the update is forced, don't modify
      * anything but only notify the new clock point. */
     if (system_now != VLC_TICK_MAX)
     {
-        vlc_clock_master_update_coeff(clock, system_now, ts, rate);
-        main_clock->last = clock_point_Create(system_now, ts);
+        vlc_clock_master_update_coeff(clock, ctx, system_now, ts, rate);
+        ctx->last = clock_point_Create(system_now, ts);
     }
 
     /* Fix the reported ts if both master and slaves source are delayed. This
@@ -338,6 +490,12 @@ static void vlc_clock_master_reset(vlc_clock_t *clock)
     if (main_clock->tracer != NULL && clock->track_str_id != NULL)
         vlc_tracer_TraceEvent(main_clock->tracer, "RENDER", clock->track_str_id,
                               "reset_user");
+
+    /* Attach to the last and main context in case of reset */
+    struct vlc_clock_context *ctx = main_clock->context;
+    if (clock->context != NULL && clock->context != ctx)
+        vlc_clock_switch_context(clock, ctx);
+
     vlc_clock_main_reset(main_clock);
 
     assert(main_clock->delay <= 0);
@@ -387,12 +545,13 @@ static vlc_tick_t vlc_clock_master_set_delay(vlc_clock_t *clock, vlc_tick_t dela
 }
 
 static vlc_tick_t
-vlc_clock_monotonic_to_system(vlc_clock_t *clock, vlc_tick_t now,
-                              vlc_tick_t ts, double rate)
+vlc_clock_monotonic_to_system(vlc_clock_t *clock, struct vlc_clock_context *ctx,
+                              vlc_tick_t now, vlc_tick_t ts, double rate)
 {
     vlc_clock_main_t *main_clock = clock->owner;
 
-    if (clock->priority < main_clock->wait_sync_ref_priority)
+    if (clock->priority < main_clock->wait_sync_ref_priority
+     && ctx == main_clock->context)
     {
         /* 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
@@ -416,46 +575,50 @@ vlc_clock_monotonic_to_system(vlc_clock_t *clock, vlc_tick_t now,
             __MAX(input_delay, main_clock->output_dejitter);
 
         main_clock->wait_sync_ref_priority = clock->priority;
-        main_clock->wait_sync_ref = clock_point_Create(now + delay, ts);
+        ctx->wait_sync_ref = clock_point_Create(now + delay, ts);
     }
-    return (ts - main_clock->wait_sync_ref.stream) / rate
-        + main_clock->wait_sync_ref.system;
+
+    assert(ctx->wait_sync_ref.stream != VLC_TICK_INVALID);
+
+    return (ts - ctx->wait_sync_ref.stream) / rate + ctx->wait_sync_ref.system;
 }
 
 static vlc_tick_t vlc_clock_slave_to_system(vlc_clock_t *clock,
+                                            struct vlc_clock_context *ctx,
                                             vlc_tick_t now, vlc_tick_t ts,
                                             double rate)
 {
     vlc_clock_main_t *main_clock = clock->owner;
 
-    vlc_tick_t system = main_stream_to_system(main_clock, ts);
+    vlc_tick_t system = context_stream_to_system(ctx, ts);
     if (system == VLC_TICK_INVALID)
     {
         /* We don't have a master sync point, let's fallback to a monotonic ref
          * point */
-        system = vlc_clock_monotonic_to_system(clock, now, ts, rate);
+        system = vlc_clock_monotonic_to_system(clock, ctx, now, ts, rate);
     }
 
     return system + (clock->delay - main_clock->delay) * rate;
 }
 
 static vlc_tick_t vlc_clock_master_to_system(vlc_clock_t *clock,
+                                             struct vlc_clock_context *ctx,
                                              vlc_tick_t now, vlc_tick_t ts,
                                              double rate)
 {
-    vlc_clock_main_t *main_clock = clock->owner;
-    vlc_tick_t system = main_stream_to_system(main_clock, ts);
+    vlc_tick_t system = context_stream_to_system(ctx, ts);
     if (system == VLC_TICK_INVALID)
     {
         /* We don't have a master sync point, let's fallback to a monotonic ref
          * point */
-        system = vlc_clock_monotonic_to_system(clock, now, ts, rate);
+        system = vlc_clock_monotonic_to_system(clock, ctx, now, ts, rate);
     }
 
     return system;
 }
 
 static vlc_tick_t vlc_clock_slave_update(vlc_clock_t *clock,
+                                         struct vlc_clock_context *ctx,
                                          vlc_tick_t system_now,
                                          vlc_tick_t ts, double rate,
                                          unsigned frame_rate,
@@ -470,7 +633,7 @@ 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, system_now, ts, rate);
+    vlc_tick_t computed = clock->ops->to_system(clock, ctx, system_now, ts, rate);
 
     vlc_tick_t drift = computed - system_now;
     vlc_clock_on_update(clock, computed, ts, drift, rate,
@@ -481,9 +644,14 @@ static vlc_tick_t vlc_clock_slave_update(vlc_clock_t *clock,
 static void vlc_clock_slave_reset(vlc_clock_t *clock)
 {
     vlc_clock_main_t *main_clock = clock->owner;
+
+    /* Attach to the last and main context in case of reset */
+    struct vlc_clock_context *ctx = main_clock->context;
+    if (clock->context != NULL && clock->context != ctx)
+        vlc_clock_switch_context(clock, ctx);
+
     main_clock->wait_sync_ref_priority = UINT_MAX;
-    main_clock->wait_sync_ref =
-        clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
+    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);
@@ -548,6 +716,13 @@ vlc_clock_main_t *vlc_clock_main_New(struct vlc_logger *parent_logger, struct vl
     if (main_clock == NULL)
         return NULL;
 
+    struct vlc_clock_context *ctx = main_clock->context = context_new();
+    if (unlikely(ctx == NULL))
+    {
+        free(main_clock);
+        return NULL;
+    }
+
     main_clock->logger = vlc_LogHeaderCreate(parent_logger, "clock");
     main_clock->tracer = parent_tracer;
 
@@ -556,25 +731,21 @@ vlc_clock_main_t *vlc_clock_main_New(struct vlc_logger *parent_logger, struct vl
     main_clock->input_master = main_clock->master = NULL;
     main_clock->rc = 1;
 
-    main_clock->coeff = 1.0f;
-    main_clock->rate = 1.0f;
-    main_clock->offset = VLC_TICK_INVALID;
     main_clock->delay = 0;
 
     main_clock->first_pcr =
         clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
     main_clock->wait_sync_ref_priority = UINT_MAX;
-    main_clock->wait_sync_ref = main_clock->last =
-        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;
 
     AvgInit(&main_clock->coeff_avg, 10);
-    AvgResetAndFill(&main_clock->coeff_avg, main_clock->coeff);
+    AvgResetAndFill(&main_clock->coeff_avg, ctx->coeff);
 
     vlc_vector_init(&main_clock->listeners);
+    vlc_list_init(&main_clock->prev_contexts);
 
     return main_clock;
 }
@@ -584,6 +755,7 @@ void vlc_clock_main_Reset(vlc_clock_main_t *main_clock)
     vlc_mutex_assert(&main_clock->lock);
 
     vlc_clock_main_reset(main_clock);
+
     main_clock->first_pcr =
         clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
 }
@@ -592,14 +764,30 @@ void vlc_clock_main_SetFirstPcr(vlc_clock_main_t *main_clock,
                                 vlc_tick_t system_now, vlc_tick_t ts)
 {
     vlc_mutex_assert(&main_clock->lock);
+    struct vlc_clock_context *ctx = main_clock->context;
 
-    if (main_clock->first_pcr.system == VLC_TICK_INVALID)
+    /* Keep the old context if it allows to convert via master or monotonic */
+    if (main_clock->first_pcr.system != VLC_TICK_INVALID
+     && (ctx->last.system != VLC_TICK_INVALID ||
+         ctx->wait_sync_ref.stream != VLC_TICK_INVALID)
+     && !vlc_list_is_empty(&ctx->using_clocks))
     {
-        main_clock->first_pcr = clock_point_Create(system_now, ts);
-        main_clock->wait_sync_ref_priority = UINT_MAX;
-        main_clock->wait_sync_ref =
-            clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
+        struct vlc_clock_context *newctx = context_new();
+        if (newctx != NULL)
+        {
+            newctx->clock_id = ctx->clock_id + 1;
+            vlc_list_append(&ctx->node, &main_clock->prev_contexts);
+            main_clock->context = ctx = newctx;
+        }
+        vlc_warning(main_clock->logger, "new clock context(%u) @%"PRId64,
+                    ctx->clock_id, ts);
     }
+    vlc_clock_main_reset(main_clock);
+
+    main_clock->first_pcr = clock_point_Create(system_now, ts);
+    main_clock->wait_sync_ref_priority = UINT_MAX;
+    ctx->wait_sync_ref =
+        clock_point_Create(VLC_TICK_INVALID, VLC_TICK_INVALID);
 }
 
 void vlc_clock_main_SetInputDejitter(vlc_clock_main_t *main_clock,
@@ -631,20 +819,21 @@ void vlc_clock_main_ChangePause(vlc_clock_main_t *main_clock, vlc_tick_t now,
         return;
     }
 
+    struct vlc_clock_context *ctx = main_clock->context;
     /**
      * Only apply a delay if the clock has a reference point to avoid
      * messing up the timings if the stream was paused then seeked
      */
     const vlc_tick_t delay = now - main_clock->pause_date;
-    if (main_clock->last.system != VLC_TICK_INVALID)
+    if (ctx->last.system != VLC_TICK_INVALID)
     {
-        main_clock->last.system += delay;
-        main_clock->offset += delay;
+        ctx->last.system += delay;
+        ctx->offset += delay;
     }
     if (main_clock->first_pcr.system != VLC_TICK_INVALID)
         main_clock->first_pcr.system += delay;
-    if (main_clock->wait_sync_ref.system != VLC_TICK_INVALID)
-        main_clock->wait_sync_ref.system += delay;
+    if (ctx->wait_sync_ref.system != VLC_TICK_INVALID)
+        ctx->wait_sync_ref.system += delay;
     main_clock->pause_date = VLC_TICK_INVALID;
     vlc_cond_broadcast(&main_clock->cond);
 }
@@ -658,6 +847,11 @@ void vlc_clock_main_Delete(vlc_clock_main_t *main_clock)
     assert(main_clock->listeners.size == 0);
     vlc_vector_destroy(&main_clock->listeners);
 
+    assert(vlc_list_is_empty(&main_clock->prev_contexts));
+
+    assert(vlc_list_is_empty(&main_clock->context->using_clocks));
+    free(main_clock->context);
+
     free(main_clock);
 }
 
@@ -676,7 +870,10 @@ vlc_tick_t vlc_clock_Update(vlc_clock_t *clock, vlc_tick_t system_now,
                             vlc_tick_t ts, double rate)
 {
     AssertLocked(clock);
-    return clock->ops->update(clock, system_now, ts, rate, 0, 0);
+    struct vlc_clock_context *ctx =
+        vlc_clock_get_context(clock, system_now, ts, true);
+
+    return clock->ops->update(clock, ctx, system_now, ts, rate, 0, 0);
 }
 
 vlc_tick_t vlc_clock_UpdateVideo(vlc_clock_t *clock, vlc_tick_t system_now,
@@ -684,7 +881,11 @@ vlc_tick_t vlc_clock_UpdateVideo(vlc_clock_t *clock, vlc_tick_t system_now,
                                  unsigned frame_rate, unsigned frame_rate_base)
 {
     AssertLocked(clock);
-    return clock->ops->update(clock, system_now, ts, rate, frame_rate, frame_rate_base);
+    struct vlc_clock_context *ctx =
+        vlc_clock_get_context(clock, system_now, ts, true);
+
+    return clock->ops->update(clock, ctx, system_now, ts, rate,
+                              frame_rate, frame_rate_base);
 }
 
 void vlc_clock_Reset(vlc_clock_t *clock)
@@ -701,10 +902,17 @@ vlc_tick_t vlc_clock_SetDelay(vlc_clock_t *clock, vlc_tick_t delay)
 
 vlc_tick_t vlc_clock_ConvertToSystem(vlc_clock_t *clock,
                                      vlc_tick_t system_now, vlc_tick_t ts,
-                                     double rate)
+                                     double rate, uint32_t *clock_id)
 {
     AssertLocked(clock);
-    return clock->ops->to_system(clock, system_now, ts, rate);
+    struct vlc_clock_context *ctx =
+        vlc_clock_get_context(clock, system_now, ts, false);
+    if (clock_id != NULL)
+        *clock_id = ctx->clock_id;
+
+    clock->last_conversion =
+        clock->ops->to_system(clock, ctx, system_now, ts, rate);
+    return clock->last_conversion;
 }
 
 static const struct vlc_clock_ops master_ops = {
@@ -723,7 +931,7 @@ static const struct vlc_clock_ops slave_ops = {
 
 static vlc_clock_t *vlc_clock_main_Create(vlc_clock_main_t *main_clock,
                                           const char* track_str_id,
-                                          unsigned priority,
+                                          bool input, unsigned priority,
                                           const struct vlc_clock_cbs *cbs,
                                           void *cbs_data)
 {
@@ -738,6 +946,20 @@ 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;
+
+    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);
+    }
 
     return clock;
 }
@@ -750,7 +972,8 @@ vlc_clock_t *vlc_clock_main_CreateMaster(vlc_clock_main_t *main_clock,
     vlc_mutex_assert(&main_clock->lock);
 
     /* The master has always the 0 priority */
-    vlc_clock_t *clock = vlc_clock_main_Create(main_clock, track_str_id, 0, cbs, cbs_data);
+    vlc_clock_t *clock = vlc_clock_main_Create(main_clock, track_str_id, false,
+                                               0, cbs, cbs_data);
     if (!clock)
         return NULL;
 
@@ -770,9 +993,11 @@ vlc_clock_t *vlc_clock_main_CreateMaster(vlc_clock_main_t *main_clock,
 vlc_clock_t *vlc_clock_main_CreateInputMaster(vlc_clock_main_t *main_clock)
 {
     vlc_mutex_assert(&main_clock->lock);
+    struct vlc_clock_context *ctx = main_clock->context;
 
     /* The master has always the 0 priority */
-    vlc_clock_t *clock = vlc_clock_main_Create(main_clock, "input", 0, NULL, NULL);
+    vlc_clock_t *clock = vlc_clock_main_Create(main_clock, "input", true, 
+                                               0, NULL, NULL);
     if (!clock)
         return NULL;
 
@@ -780,7 +1005,7 @@ 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(main_clock->offset == VLC_TICK_INVALID);
+    assert(ctx->offset == VLC_TICK_INVALID); (void) ctx;
 
     /* Override the master ES clock if it exists */
     if (main_clock->master != NULL)
@@ -793,6 +1018,22 @@ vlc_clock_t *vlc_clock_main_CreateInputMaster(vlc_clock_main_t *main_clock)
     return clock;
 }
 
+vlc_clock_t *vlc_clock_main_CreateInputSlave(vlc_clock_main_t *main_clock)
+{
+    vlc_mutex_assert(&main_clock->lock);
+
+    /* The input has always the 0 priority */
+    vlc_clock_t *clock = vlc_clock_main_Create(main_clock, "input", true,
+                                               0, NULL, NULL);
+    if (!clock)
+        return NULL;
+
+    clock->ops = &slave_ops;
+    main_clock->rc++;
+
+    return clock;
+}
+
 vlc_clock_t *vlc_clock_main_CreateSlave(vlc_clock_main_t *main_clock,
                                         const char* track_str_id,
                                         enum es_format_category_e cat,
@@ -818,8 +1059,8 @@ vlc_clock_t *vlc_clock_main_CreateSlave(vlc_clock_main_t *main_clock,
             break;
     }
 
-    vlc_clock_t *clock = vlc_clock_main_Create(main_clock, track_str_id, priority, cbs,
-                                               cbs_data);
+    vlc_clock_t *clock = vlc_clock_main_Create(main_clock, track_str_id, false,
+                                               priority, cbs, cbs_data);
     if (!clock)
         return NULL;
 
@@ -839,6 +1080,10 @@ void vlc_clock_Delete(vlc_clock_t *clock)
 {
     vlc_clock_main_t *main_clock = clock->owner;
     vlc_mutex_lock(&main_clock->lock);
+
+    if (clock->context != NULL)
+        vlc_clock_remove_current_context(clock);
+
     if (clock == main_clock->input_master)
     {
         /* The input master must be the last clock to be deleted */


=====================================
src/clock/clock.h
=====================================
@@ -170,6 +170,17 @@ vlc_clock_t *vlc_clock_main_CreateMaster(vlc_clock_main_t *main_clock,
  */
 vlc_clock_t *vlc_clock_main_CreateInputMaster(vlc_clock_main_t *main_clock);
 
+/**
+ * This function creates a new input slave vlc_clock_t interface
+ *
+ * @warning There can be only one input, slave or master, at a given time.
+ *
+ * You must use vlc_clock_Delete to free it.
+ *
+ * @param main_clock the locked main_clock
+ */
+vlc_clock_t *vlc_clock_main_CreateInputSlave(vlc_clock_main_t *main_clock);
+
 /**
  * This function creates a new slave vlc_clock_t interface
  *
@@ -312,10 +323,11 @@ vlc_clock_RemoveListener(vlc_clock_t *clock, vlc_clock_listener_id *listener_id)
  * This function converts a timestamp from stream to system
  *
  * @param clock the locked clock used by the source
+ * @param clock_id pointer to the clock id used for conversion. Can be NULL.
  * @return the valid system time
  */
 vlc_tick_t vlc_clock_ConvertToSystem(vlc_clock_t *clock,
                                      vlc_tick_t system_now, vlc_tick_t ts,
-                                     double rate);
+                                     double rate, uint32_t *clock_id);
 
 #endif /*CLOCK_H*/


=====================================
src/clock/input_clock.c
=====================================
@@ -144,7 +144,7 @@ static vlc_tick_t ClockSystemToStream( input_clock_t *, vlc_tick_t i_system );
 
 static vlc_tick_t ClockGetTsOffset( input_clock_t * );
 
-static void UpdateListener( input_clock_t *cl )
+static void UpdateListener( input_clock_t *cl, bool discontinuity )
 {
     if (cl->listener.cbs == NULL)
         return;
@@ -156,7 +156,7 @@ static void UpdateListener( input_clock_t *cl )
     /* The returned drift value is ignored for now since a different
      * value is computed by the input_clock. */
     cl->listener.cbs->update(cl->listener.opaque, system_expected,
-                             cl->last.stream, cl->rate);
+                             cl->last.stream, cl->rate, discontinuity);
 }
 
 /*****************************************************************************
@@ -224,6 +224,7 @@ vlc_tick_t input_clock_Update( input_clock_t *cl, vlc_object_t *p_log,
                          vlc_tick_t i_ck_stream, vlc_tick_t i_ck_system )
 {
     bool b_reset_reference = false;
+    bool discontinuity = false;
 
     assert( i_ck_stream != VLC_TICK_INVALID && i_ck_system != VLC_TICK_INVALID );
 
@@ -255,6 +256,7 @@ vlc_tick_t input_clock_Update( input_clock_t *cl, vlc_object_t *p_log,
             msg_Warn(p_log, "feeding synchro with a new reference point trying"
                      " to recover from clock gap");
             b_reset_reference= true;
+            discontinuity = true;
         }
     }
     cl->b_origin_changed = false;
@@ -313,7 +315,7 @@ vlc_tick_t input_clock_Update( input_clock_t *cl, vlc_object_t *p_log,
         cl->late.i_index = ( cl->late.i_index + 1 ) % INPUT_CLOCK_LATE_COUNT;
     }
 
-    UpdateListener( cl );
+    UpdateListener( cl, discontinuity );
 
     return i_late;
 }
@@ -345,7 +347,7 @@ void input_clock_ChangeRate( input_clock_t *cl, float rate )
     }
     cl->rate = rate;
 
-    UpdateListener( cl );
+    UpdateListener( cl, false );
 }
 
 /*****************************************************************************
@@ -364,7 +366,7 @@ void input_clock_ChangePause( input_clock_t *cl, bool b_paused, vlc_tick_t i_dat
             cl->ref.system += i_duration;
             cl->last.system += i_duration;
 
-            UpdateListener( cl );
+            UpdateListener( cl, false );
         }
     }
     cl->i_pause_date = i_date;
@@ -419,7 +421,7 @@ void input_clock_ChangeSystemOrigin( input_clock_t *cl, vlc_tick_t i_system )
     cl->ref.system += i_offset;
     cl->last.system += i_offset;
 
-    UpdateListener( cl );
+    UpdateListener( cl, false );
 }
 
 #warning "input_clock_SetJitter needs more work"


=====================================
src/clock/input_clock.h
=====================================
@@ -49,12 +49,13 @@ struct vlc_input_clock_cbs {
      * \param ck_system     time reference for the buffering progress
      * \param ck_stream     progress of the buffering in tick
      * \param rate          current playback rate for the buffering
+     * \param discontinuity PCR discontinuity with the previous update
      *
      * \return              how much time the playback has drifted from
      *                      the main clock
      */
     vlc_tick_t (*update)(void *opaque, vlc_tick_t ck_system,
-                         vlc_tick_t ck_stream, double rate);
+                         vlc_tick_t ck_stream, double rate, bool discontinuity);
 
     /**
      * Notify the listener that the buffering needed a reset.
@@ -109,6 +110,7 @@ void input_clock_Delete(input_clock_t *);
 vlc_tick_t input_clock_Update( input_clock_t *clock, vlc_object_t *p_log,
                             bool b_can_pace_control, bool b_buffering_allowed,
                             vlc_tick_t i_clock, vlc_tick_t i_system );
+
 /**
  * This function will reset the drift of a input_clock_t.
  *


=====================================
src/input/decoder.c
=====================================
@@ -991,7 +991,7 @@ static vlc_tick_t ModuleThread_GetDisplayDate( decoder_t *p_dec,
 
     vlc_clock_Lock( p_owner->p_clock );
     vlc_tick_t conv_ts =
-        vlc_clock_ConvertToSystem( p_owner->p_clock, system_now, i_ts, rate );
+        vlc_clock_ConvertToSystem( p_owner->p_clock, system_now, i_ts, rate, NULL );
     vlc_clock_Unlock( p_owner->p_clock );
     return conv_ts;
 }


=====================================
src/input/es_out.c
=====================================
@@ -1240,10 +1240,17 @@ static void EsOutSendEsEvent(es_out_sys_t *p_sys, es_out_id_t *es, int action,
 
 static vlc_tick_t
 ClockListenerUpdate(void *opaque, vlc_tick_t ck_system,
-                    vlc_tick_t ck_stream, double rate)
+                    vlc_tick_t ck_stream, double rate, bool discontinuity)
 {
     es_out_pgrm_t *pgrm = opaque;
     vlc_clock_Lock(pgrm->clocks.input);
+
+    if (discontinuity)
+    {
+        const vlc_tick_t current_date = vlc_tick_now();
+        vlc_clock_main_SetFirstPcr(pgrm->clocks.main, current_date, ck_stream);
+    }
+
     vlc_tick_t drift =
         vlc_clock_Update(pgrm->clocks.input, ck_system, ck_stream, rate);
     vlc_clock_Unlock(pgrm->clocks.input);
@@ -1259,7 +1266,6 @@ ClockListenerReset(void *opaque)
     vlc_clock_Unlock(pgrm->clocks.input);
 }
 
-
 static void EsOutProgramHandleClockSource(es_out_sys_t *p_sys, es_out_pgrm_t *p_pgrm)
 {
     input_thread_t *p_input = p_sys->p_input;
@@ -1327,8 +1333,8 @@ static void EsOutProgramHandleClockSource(es_out_sys_t *p_sys, es_out_pgrm_t *p_
     if (p_pgrm->active_clock_source != VLC_CLOCK_MASTER_INPUT)
     {
         vlc_clock_main_Lock(p_pgrm->clocks.main);
-        p_pgrm->clocks.input = vlc_clock_main_CreateSlave(
-            p_pgrm->clocks.main, "pcr", UNKNOWN_ES, NULL, NULL);
+        p_pgrm->clocks.input =
+            vlc_clock_main_CreateInputSlave(p_pgrm->clocks.main);
         vlc_clock_main_Unlock(p_pgrm->clocks.main);
 
         if (p_pgrm->clocks.input != NULL)


=====================================
src/video_output/video_output.c
=====================================
@@ -85,6 +85,7 @@ typedef struct vout_thread_sys_t
 
     vlc_clock_t     *clock;
     vlc_clock_listener_id *clock_listener_id;
+    uint32_t clock_id;
     float           rate;
     vlc_tick_t      delay;
 
@@ -982,25 +983,37 @@ static picture_t *PreparePicture(vout_thread_sys_t *vout, bool reuse_decoded,
             decoded = picture_fifo_Pop(sys->decoder_fifo);
 
             if (decoded) {
-                if (is_late_dropped && !decoded->b_force)
+                const vlc_tick_t system_now = vlc_tick_now();
+                uint32_t clock_id;
+                vlc_clock_Lock(sys->clock);
+                const vlc_tick_t system_pts =
+                    vlc_clock_ConvertToSystem(sys->clock, system_now,
+                                              decoded->date, sys->rate, &clock_id);
+                vlc_clock_Unlock(sys->clock);
+                if (clock_id != sys->clock_id)
                 {
-                    const vlc_tick_t system_now = vlc_tick_now();
-                    vlc_clock_Lock(sys->clock);
-                    const vlc_tick_t system_pts =
-                        vlc_clock_ConvertToSystem(sys->clock, system_now,
-                                                  decoded->date, sys->rate);
-                    vlc_clock_Unlock(sys->clock);
-
-                    if (IsPictureLate(vout, decoded, system_now, system_pts))
-                    {
-                        picture_Release(decoded);
-                        vout_statistic_AddLost(&sys->statistic, 1);
+                    sys->clock_id = clock_id;
+                    msg_Dbg(&vout->obj, "Using a new clock context (%u), "
+                            "flusing static filters", clock_id);
+
+                    /* Most deinterlace modules can't handle a PTS
+                     * discontinuity, so flush them.
+                     *
+                     * FIXME: Pass a discontinuity flag and handle it in
+                     * deinterlace modules. */
+                    filter_chain_VideoFlush(sys->filter.chain_static);
+                }
 
-                        /* A picture dropped means discontinuity for the
-                         * filters and we need to notify eg. deinterlacer. */
-                        filter_chain_VideoFlush(sys->filter.chain_static);
-                        continue;
-                    }
+                if (is_late_dropped && !decoded->b_force
+                 && IsPictureLate(vout, decoded, system_now, system_pts))
+                {
+                    picture_Release(decoded);
+                    vout_statistic_AddLost(&sys->statistic, 1);
+
+                    /* A picture dropped means discontinuity for the
+                     * filters and we need to notify eg. deinterlacer. */
+                    filter_chain_VideoFlush(sys->filter.chain_static);
+                    continue;
                 }
 
                 if (!VideoFormatIsCropArEqual(&decoded->format, &sys->filter.src_fmt))
@@ -1152,7 +1165,7 @@ static int PrerenderPicture(vout_thread_sys_t *sys, picture_t *filtered,
         vlc_clock_Lock(sys->clock);
         render_subtitle_date = filtered->date <= VLC_TICK_0 ? system_now :
             vlc_clock_ConvertToSystem(sys->clock, system_now, filtered->date,
-                                      sys->rate);
+                                      sys->rate, NULL);
         vlc_clock_Unlock(sys->clock);
     }
 
@@ -1325,7 +1338,7 @@ static int RenderPicture(vout_thread_sys_t *sys, bool render_now)
     const vlc_tick_t pts = todisplay->date;
     vlc_clock_Lock(sys->clock);
     vlc_tick_t system_pts = render_now ? system_now :
-        vlc_clock_ConvertToSystem(sys->clock, system_now, pts, sys->rate);
+        vlc_clock_ConvertToSystem(sys->clock, system_now, pts, sys->rate, NULL);
     vlc_clock_Unlock(sys->clock);
 
     const unsigned frame_rate = todisplay->format.i_frame_rate;
@@ -1371,7 +1384,7 @@ static int RenderPicture(vout_thread_sys_t *sys, bool render_now)
                 {
                     deadline = vlc_clock_ConvertToSystem(sys->clock,
                                                          vlc_tick_now(), pts,
-                                                         sys->rate);
+                                                         sys->rate, NULL);
                     if (deadline > max_deadline)
                         deadline = max_deadline;
                 }
@@ -1479,7 +1492,7 @@ static bool UpdateCurrentPicture(vout_thread_sys_t *sys)
     vlc_clock_Lock(sys->clock);
     const vlc_tick_t system_swap_current =
         vlc_clock_ConvertToSystem(sys->clock, system_now,
-                                  sys->displayed.current->date, sys->rate);
+                                  sys->displayed.current->date, sys->rate, NULL);
     vlc_clock_Unlock(sys->clock);
 
     const vlc_tick_t render_delay = vout_chrono_GetHigh(&sys->chrono.render) + VOUT_MWAIT_TOLERANCE;
@@ -1932,6 +1945,7 @@ static void vout_ReleaseDisplay(vout_thread_sys_t *vout)
     sys->clock = NULL;
     vlc_mutex_unlock(&sys->clock_lock);
     sys->str_id = NULL;
+    sys->clock_id = 0;
 }
 
 void vout_StopDisplay(vout_thread_t *vout)
@@ -2269,6 +2283,7 @@ int vout_Request(const vout_configuration_t *cfg, vlc_video_context *vctx, input
     sys->delay = 0;
     sys->rate = 1.f;
     sys->str_id = cfg->str_id;
+    sys->clock_id = 0;
 
     vlc_mutex_lock(&sys->clock_lock);
     sys->clock = cfg->clock;


=====================================
src/video_output/vout_subpictures.c
=====================================
@@ -776,11 +776,13 @@ static size_t spu_channel_UpdateDates(struct spu_channel *channel,
         assert(entry);
 
         entry->start = vlc_clock_ConvertToSystem(channel->clock, system_now,
-                                                 entry->orgstart, channel->rate);
+                                                 entry->orgstart, channel->rate,
+                                                 NULL);
 
         entry->stop =
             vlc_clock_ConvertToSystem(channel->clock, system_now,
-                                      entry->orgstop, channel->rate);
+                                      entry->orgstop, channel->rate,
+                                      NULL);
     }
     vlc_clock_Unlock(channel->clock);
 
@@ -2125,10 +2127,10 @@ void spu_PutSubpicture(spu_t *spu, subpicture_t *subpic)
         vlc_clock_Lock(channel->clock);
         subpic->i_start =
             vlc_clock_ConvertToSystem(channel->clock, system_now,
-                                      orgstart, channel->rate);
+                                      orgstart, channel->rate, NULL);
         subpic->i_stop =
             vlc_clock_ConvertToSystem(channel->clock, system_now,
-                                      orgstop, channel->rate);
+                                      orgstop, channel->rate, NULL);
         vlc_clock_Unlock(channel->clock);
 
         spu_channel_EarlyRemoveLate(sys, channel, system_now);


=====================================
test/src/clock/clock.c
=====================================
@@ -398,7 +398,7 @@ static void play_scenario(libvlc_int_t *vlc, struct vlc_tracer *tracer,
                 vlc_clock_Lock(ctx.slave);
                 vlc_tick_t play_date =
                     vlc_clock_ConvertToSystem(ctx.slave, video_system, video_ts,
-                                              1.0f);
+                                              1.0f, NULL);
                 vlc_clock_Update(ctx.slave, play_date, video_ts, 1.0f);
                 vlc_clock_Unlock(ctx.slave);
                 video_system += video_increment;
@@ -561,7 +561,7 @@ static void normal_check(const struct clock_ctx *ctx, size_t update_count,
     vlc_clock_Lock(ctx->slave);
     vlc_tick_t converted =
         vlc_clock_ConvertToSystem(ctx->slave, expected_system_end,
-                                  stream_end, 1.0f);
+                                  stream_end, 1.0f, NULL);
     vlc_clock_Unlock(ctx->slave);
     assert(converted == expected_system_end);
 }
@@ -643,7 +643,7 @@ static void drift_check(const struct clock_ctx *ctx, size_t update_count,
     vlc_clock_Lock(ctx->slave);
     vlc_tick_t converted =
         vlc_clock_ConvertToSystem(ctx->slave, expected_system_end,
-                                  stream_end, 1.0f);
+                                  stream_end, 1.0f, NULL);
     vlc_clock_Unlock(ctx->slave);
 
     assert(converted - expected_system_end == scenario->total_drift_duration);
@@ -683,7 +683,7 @@ static void pause_common(const struct clock_ctx *ctx, vlc_clock_t *updater)
 
     {
         vlc_clock_Lock(ctx->slave);
-        vlc_tick_t converted = vlc_clock_ConvertToSystem(ctx->slave, system, ctx->stream_start, 1.0f);
+        vlc_tick_t converted = vlc_clock_ConvertToSystem(ctx->slave, system, ctx->stream_start, 1.0f, NULL);
         assert(converted == system);
         vlc_clock_Unlock(ctx->slave);
     }
@@ -702,7 +702,7 @@ static void pause_common(const struct clock_ctx *ctx, vlc_clock_t *updater)
     system += 1;
 
     vlc_clock_Lock(ctx->slave);
-    vlc_tick_t converted = vlc_clock_ConvertToSystem(ctx->slave, system, ctx->stream_start, 1.0f);
+    vlc_tick_t converted = vlc_clock_ConvertToSystem(ctx->slave, system, ctx->stream_start, 1.0f, NULL);
     vlc_clock_Unlock(ctx->slave);
     assert(converted == system_start + pause_duration);
 }
@@ -734,7 +734,7 @@ static void convert_paused_common(const struct clock_ctx *ctx, vlc_clock_t *upda
     system += 1;
 
     vlc_clock_Lock(ctx->slave);
-    vlc_tick_t converted = vlc_clock_ConvertToSystem(ctx->slave, system, ctx->stream_start, 1.0f);
+    vlc_tick_t converted = vlc_clock_ConvertToSystem(ctx->slave, system, ctx->stream_start, 1.0f, NULL);
     vlc_clock_Unlock(ctx->slave);
     assert(converted == system_start);
 }
@@ -749,6 +749,72 @@ static void monotonic_convert_paused_run(const struct clock_ctx *ctx)
     convert_paused_common(ctx, ctx->slave);
 }
 
+static void contexts_run(const struct clock_ctx *ctx)
+{
+    vlc_tick_t converted;
+    vlc_tick_t system = ctx->system_start;
+    vlc_tick_t stream_context0 = 1;
+    uint32_t clock_id;
+
+    vlc_clock_main_Lock(ctx->mainclk);
+
+    /* Initial SetFirstPcr, that will initialise the default and main context */
+    vlc_clock_main_SetFirstPcr(ctx->mainclk, system, stream_context0);
+
+    /* Check that the converted point is valid */
+    converted = vlc_clock_ConvertToSystem(ctx->slave, system, stream_context0,
+                                          1.0f, &clock_id);
+    assert(clock_id == 0);
+    assert(converted == system);
+    vlc_clock_Update(ctx->slave, system, stream_context0, 1.0f);
+
+    /* Discontinuity from 1us to 30 sec */
+    vlc_tick_t system_context0 = system;
+    vlc_tick_t stream_context1 = VLC_TICK_FROM_SEC(30);
+    system += VLC_TICK_FROM_MS(100);
+    vlc_clock_main_SetFirstPcr(ctx->mainclk, system, stream_context1);
+
+    /* Check that we can use the new context (or new origin) */
+    converted = vlc_clock_ConvertToSystem(ctx->slave, system, stream_context1,
+                                          1.0f, &clock_id);
+    assert(clock_id == 1);
+    assert(converted == system);
+
+    /* Check that we can still use the old context when converting a point
+     * closer to the original context */
+    converted = vlc_clock_ConvertToSystem(ctx->slave, system,
+                                          VLC_TICK_FROM_MS(10) + stream_context0,
+                                          1.0f, &clock_id);
+    assert(clock_id == 0);
+    assert(converted == system_context0 + VLC_TICK_FROM_MS(10));
+
+    /* Update on the newest context will cause previous contexts to be removed */
+    vlc_clock_Update(ctx->slave, system, stream_context1, 1.0f);
+
+    /* Discontinuity back to 1us */
+    system += VLC_TICK_FROM_MS(100);
+    vlc_tick_t stream_context2 = 1;
+    vlc_clock_main_SetFirstPcr(ctx->mainclk, system, stream_context2);
+
+    /* Check that we can use the new context (or new origin) */
+    converted = vlc_clock_ConvertToSystem(ctx->slave, system, stream_context2,
+                                          1.0f, &clock_id);
+    assert(clock_id == 2);
+    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);
+
+    /* Check that the same conversion will output a different result now that
+     * the old contexts are removed */
+    system += VLC_TICK_FROM_MS(100);
+    converted = vlc_clock_ConvertToSystem(ctx->slave, system, stream_context1, 1.0f,
+                                          &clock_id);
+    assert(clock_id == 2);
+    assert(converted != system_context0);
+
+    vlc_clock_main_Unlock(ctx->mainclk);
+}
+
 #define VLC_TICK_12H VLC_TICK_FROM_SEC(12 * 60 * 60)
 #define VLC_TICK_2H VLC_TICK_FROM_SEC(2 * 60 * 60)
 #define DEFAULT_STREAM_INCREMENT VLC_TICK_FROM_MS(100)
@@ -849,6 +915,13 @@ static struct clock_scenario clock_scenarios[] = {
     .run = monotonic_convert_paused_run,
     .disable_jitter = true,
 },
+{
+    .name = "contexts",
+    .desc = "switching contexts is handled",
+    .type = CLOCK_SCENARIO_RUN,
+    .run = contexts_run,
+    .disable_jitter = true,
+},
 };
 
 int main(int argc, const char *argv[])


=====================================
test/src/player/player.c
=====================================
@@ -124,7 +124,7 @@ struct report_media_attachments
     size_t count;
 };
 
-#define REPORT_LIST \
+#define PLAYER_REPORT_LIST \
     X(input_item_t *, on_current_media_changed) \
     X(enum vlc_player_state, on_state_changed) \
     X(enum vlc_player_error, on_error_changed) \
@@ -154,6 +154,14 @@ struct report_media_attachments
     X(struct report_media_subitems, on_media_subitems_changed) \
     X(struct report_media_attachments, on_media_attachments_added) \
 
+struct report_aout_first_pts
+{
+    vlc_tick_t first_pts;
+};
+
+#define REPORT_LIST \
+    X(vlc_tick_t, on_aout_first_pts) \
+
 struct report_timer
 {
     enum
@@ -179,12 +187,14 @@ struct timer_state
 };
 
 #define X(type, name) typedef struct VLC_VECTOR(type) vec_##name;
+PLAYER_REPORT_LIST
 REPORT_LIST
 #undef X
 
 #define X(type, name) vec_##name name;
 struct reports
 {
+PLAYER_REPORT_LIST
 REPORT_LIST
 };
 #undef X
@@ -193,6 +203,7 @@ static inline void
 reports_init(struct reports *report)
 {
 #define X(type, name) vlc_vector_init(&report->name);
+PLAYER_REPORT_LIST
 REPORT_LIST
 #undef X
 }
@@ -220,6 +231,7 @@ struct media_params
     vlc_tick_t pts_delay;
 
     const char *config;
+    const char *discontinuities;
 };
 
 #define DEFAULT_MEDIA_PARAMS(param_length) { \
@@ -243,10 +255,19 @@ struct media_params
     .null_names = false, \
     .pts_delay = DEFAULT_PTS_DELAY, \
     .config = NULL, \
+    .discontinuities = NULL, \
 }
 
+#define DISABLE_VIDEO_OUTPUT (1 << 0)
+#define DISABLE_AUDIO_OUTPUT (1 << 1)
+#define DISABLE_VIDEO        (1 << 2)
+#define DISABLE_AUDIO        (1 << 3)
+#define AUDIO_INSTANT_DRAIN  (1 << 4)
+
 struct ctx
 {
+    int flags;
+
     libvlc_instance_t *vlc;
     vlc_player_t *player;
     vlc_player_listener_id *listener;
@@ -748,6 +769,7 @@ ctx_reset(struct ctx *ctx)
 #undef FOREACH_VEC
 
 #define X(type, name) vlc_vector_clear(&ctx->report.name);
+PLAYER_REPORT_LIST
 REPORT_LIST
 #undef X
 
@@ -786,7 +808,7 @@ create_mock_media(const char *name, const struct media_params *params)
         "video_frame_rate=%u;video_frame_rate_base=%u;"
         "title_count=%zu;chapter_count=%zu;"
         "can_seek=%d;can_pause=%d;error=%d;null_names=%d;pts_delay=%"PRId64";"
-        "config=%s;attachment_count=%zu",
+        "config=%s;discontinuities=%s;attachment_count=%zu",
         params->track_count[VIDEO_ES], params->track_count[AUDIO_ES],
         params->track_count[SPU_ES], params->program_count,
         params->video_packetized, params->audio_packetized,
@@ -795,7 +817,9 @@ create_mock_media(const char *name, const struct media_params *params)
         params->title_count, params->chapter_count,
         params->can_seek, params->can_pause, params->error, params->null_names,
         params->pts_delay,
-        params->config ? params->config : "", params->attachment_count);
+        params->config ? params->config : "",
+        params->discontinuities ? params->discontinuities : "",
+        params->attachment_count);
     assert(ret != -1);
     input_item_t *item = input_item_New(url, name);
     assert(item);
@@ -2234,6 +2258,7 @@ static void
 ctx_destroy(struct ctx *ctx)
 {
 #define X(type, name) vlc_vector_destroy(&ctx->report.name);
+PLAYER_REPORT_LIST
 REPORT_LIST
 #undef X
     vlc_player_RemoveListener(ctx->player, ctx->listener);
@@ -2243,16 +2268,8 @@ REPORT_LIST
     libvlc_release(ctx->vlc);
 }
 
-enum ctx_flags
-{
-    DISABLE_VIDEO_OUTPUT = 1 << 0,
-    DISABLE_AUDIO_OUTPUT = 1 << 1,
-    DISABLE_VIDEO        = 1 << 2,
-    DISABLE_AUDIO        = 1 << 3,
-};
-
 static void
-ctx_init(struct ctx *ctx, enum ctx_flags flags)
+ctx_init(struct ctx *ctx, int flags)
 {
     const char * argv[] = {
         "-v",
@@ -2277,11 +2294,12 @@ ctx_init(struct ctx *ctx, enum ctx_flags flags)
 
 #define X(type, name) .name = player_##name,
     static const struct vlc_player_cbs cbs = {
-REPORT_LIST
+PLAYER_REPORT_LIST
     };
 #undef X
 
     *ctx = (struct ctx) {
+        .flags = flags,
         .vlc = vlc,
         .next_medias = VLC_VECTOR_INITIALIZER,
         .added_medias = VLC_VECTOR_INITIALIZER,
@@ -2297,6 +2315,11 @@ REPORT_LIST
     int ret = var_SetString(vlc->p_libvlc_int, "window", "wdummy");
     assert(ret == VLC_SUCCESS);
 
+    ret = var_Create(vlc->p_libvlc_int, "test-ctx", VLC_VAR_ADDRESS);
+    assert(ret == VLC_SUCCESS);
+    ret = var_SetAddress(vlc->p_libvlc_int, "test-ctx", ctx);
+    assert(ret == VLC_SUCCESS);
+
     ctx->player = vlc_player_New(VLC_OBJECT(vlc->p_libvlc_int),
                                  VLC_PLAYER_LOCK_NORMAL);
     assert(ctx->player);
@@ -2964,6 +2987,32 @@ test_attachments(struct ctx *ctx)
     test_end(ctx);
 }
 
+static void
+test_clock_discontinuities(struct ctx *ctx)
+{
+    test_log("discontinuities\n");
+
+    vlc_player_t *player = ctx->player;
+
+    struct media_params params = DEFAULT_MEDIA_PARAMS(VLC_TICK_FROM_SEC(20));
+    params.pts_delay = VLC_TICK_FROM_MS(50);
+    params.discontinuities = "(400000,2)(400000,2500000)(3000000,10000000)";
+    player_set_next_mock_media(ctx, "media1", &params);
+
+    player_start(ctx);
+
+    vec_on_aout_first_pts *vec = &ctx->report.on_aout_first_pts;
+    while (vec->size != 4)
+        vlc_player_CondWait(player, &ctx->wait);
+
+    assert(vec->data[0] == VLC_TICK_0); /* Initial PTS */
+    assert(vec->data[1] == 2); /* 1st discontinuity */
+    assert(vec->data[2] == 2500000); /* 2nd discontinuity */
+    assert(vec->data[3] == 10000000); /* 3rd discontinuity */
+
+    test_end(ctx);
+}
+
 static void
 test_audio_loudness_meter_cb(vlc_tick_t date, double momentary_loudness,
                              void *data)
@@ -3122,6 +3171,12 @@ main(void)
     test_delete_while_playback(VLC_OBJECT(ctx.vlc->p_libvlc_int), false);
 
     ctx_destroy(&ctx);
+
+    /* Test with instantaneous audio drain */
+    ctx_init(&ctx, AUDIO_INSTANT_DRAIN);
+    test_clock_discontinuities(&ctx);
+    ctx_destroy(&ctx);
+
     /* Test with --no-video */
     ctx_init(&ctx, DISABLE_VIDEO);
     test_es_selection_override(&ctx);
@@ -3144,6 +3199,8 @@ struct aout_sys
     vlc_tick_t first_pts;
     vlc_tick_t first_play_date;
     vlc_tick_t pos;
+
+    struct ctx *ctx;
 };
 
 static void aout_Play(audio_output_t *aout, block_t *block, vlc_tick_t date)
@@ -3155,6 +3212,9 @@ static void aout_Play(audio_output_t *aout, block_t *block, vlc_tick_t date)
         assert(sys->first_pts == VLC_TICK_INVALID);
         sys->first_play_date = date;
         sys->first_pts = block->i_pts;
+
+        struct ctx *ctx = sys->ctx;
+        VEC_PUSH(on_aout_first_pts, sys->first_pts);
     }
 
     aout_TimingReport(aout, sys->first_play_date + sys->pos - VLC_TICK_0,
@@ -3170,6 +3230,11 @@ static void aout_Flush(audio_output_t *aout)
     sys->first_pts = sys->first_play_date = VLC_TICK_INVALID;
 }
 
+static void aout_InstantDrain(audio_output_t *aout)
+{
+    aout_DrainedReport(aout);
+}
+
 static int aout_Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
 {
     (void) aout;
@@ -3197,6 +3262,13 @@ static int aout_Open(vlc_object_t *obj)
 
     struct aout_sys *sys = aout->sys = malloc(sizeof(*sys));
     assert(sys != NULL);
+
+    sys->ctx = var_InheritAddress(aout, "test-ctx");
+    assert(sys->ctx != NULL);
+
+    if (sys->ctx->flags & AUDIO_INSTANT_DRAIN)
+        aout->drain = aout_InstantDrain;
+
     aout_Flush(aout);
 
     return VLC_SUCCESS;



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/6e5d01d1e20259227033e9efeef79f95cd0145d8...f8b6c4e45147f3fd4a653bd1b2ed27e8595f9b63

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/6e5d01d1e20259227033e9efeef79f95cd0145d8...f8b6c4e45147f3fd4a653bd1b2ed27e8595f9b63
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