[vlc-commits] [Git][videolan/vlc][master] 20 commits: clock: also trace clock input offset

Steve Lhomme (@robUx4) gitlab at videolan.org
Wed Jun 29 08:58:59 UTC 2022



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
91f181d1 by Thomas Guillem at 2022-06-29T08:43:09+00:00
clock: also trace clock input offset

- - - - -
aad924cd by Thomas Guillem at 2022-06-29T08:43:09+00:00
clock: trace the calculated offset

- - - - -
53d8690a by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: move/hide aout_TimeGet implementation

Refs #27023

- - - - -
049885a2 by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: dec: don't require a valid time_get cb

Refs #27023

- - - - -
45774a95 by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: file: don't implement time_get

Refs #27023

- - - - -
f4b126d6 by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: amem: don't implement time_get

Refs #27023

- - - - -
c37efd75 by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: remove (now) unused aout_TimeGetDefault

Refs #27023

- - - - -
4d4dc45a by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: dec: rename aout_TimeGet to stream_TimeGet

Refs #27023

- - - - -
c2d1cb5d by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: dec: rework stream_RequestRetiming

Rename it to stream_HandleDrift() and don't handle clock updates.

Refs #27023

- - - - -
dda2830c by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: dec: factor discontinuity in a new function

Refs #27023

- - - - -
d652f402 by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: dec: initialize the stream members earlier

Refs #27023

- - - - -
ad8db30e by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: fix possible data-race

Initialize main_stream before start since it can be read once the stream
is started from an event.

Refs #27023

- - - - -
08cd94d4 by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: add a reworked timing_report event

This partially reverts commit 02293b1df3581f21be77b639153ad16207d94bec.

Don't update the audio clock from this event but store the points in a
circular buffer. The play function will update the clock according to
these points.

Refs #27023

- - - - -
be67e2a8 by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: dec: add vlc_aout_stream_UpdateLatency()

This will send all timing points to the clock.

Must be serialized with vlc_aout_stream_Play().

Refs #27023

- - - - -
72a9a5a4 by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: dec: add a cb to notify new timing points

Refs #27023

- - - - -
343b7816 by Thomas Guillem at 2022-06-29T08:43:09+00:00
decoder: update the audio stream latency when requested

Refs #27023

- - - - -
ca9025b8 by Thomas Guillem at 2022-06-29T08:43:09+00:00
aout: dec: reset the stream after the flush

Since timings point can be updated asynchronously until the flush is
processed.

Refs #27023

- - - - -
242c4954 by Thomas Guillem at 2022-06-29T08:43:09+00:00
pulse: wait for the stream to stop

This will be necessary in the next commit, in order to not send timing
points after a flush, or while paused.

Refs #27023

- - - - -
d71e7f56 by Thomas Guillem at 2022-06-29T08:43:09+00:00
pulse: use aout_TimingReport

Refs #27023

- - - - -
9eabb282 by Thomas Guillem at 2022-06-29T08:43:09+00:00
pulse: don't interpolate timing

The new aout_TimingReport() API doesn't require timing points to be in
the present.

Refs #27023

- - - - -


9 changed files:

- include/vlc_aout.h
- modules/audio_output/amem.c
- modules/audio_output/file.c
- modules/audio_output/pulse.c
- src/audio_output/aout_internal.h
- src/audio_output/dec.c
- src/audio_output/output.c
- src/clock/clock.c
- src/input/decoder.c


Changes:

=====================================
include/vlc_aout.h
=====================================
@@ -125,6 +125,7 @@
  */
 
 struct vlc_audio_output_events {
+    void (*timing_report)(audio_output_t *, vlc_tick_t system_ts, vlc_tick_t audio_ts);
     void (*drained_report)(audio_output_t *);
     void (*volume_report)(audio_output_t *, float);
     void (*mute_report)(audio_output_t *, bool);
@@ -181,7 +182,7 @@ struct audio_output
       */
 
     int (*time_get)(audio_output_t *, vlc_tick_t *delay);
-    /**< Estimates playback buffer latency (mandatory, cannot be NULL).
+    /**< Estimates playback buffer latency (can be NULL).
       *
       * This callback computes an estimation of the delay until the current
       * tail of the audio output buffer would be rendered. This is essential
@@ -189,16 +190,18 @@ struct audio_output
       * clock and the media upstream clock (if any).
       *
       * If the audio output clock is exactly synchronized with the system
-      * monotonic clock (i.e. vlc_tick_now()), then aout_TimeGetDefault() can
-      * implement this callback. In that case, drain must be implemented (since
-      * the default implementation uses the delay to wait for the end of the
-      * stream).
+      * monotonic clock (i.e. vlc_tick_now()), then this callback is not
+      * mandatory.  In that case, drain must be implemented (since the default
+      * implementation uses the delay to wait for the end of the stream).
       *
       * This callback is called before the first play() in order to get the
       * initial delay (the hw latency). Most modules won't be able to know this
       * latency before the first play. In that case, they should return -1 and
       * handle the first play() date, cf. play() documentation.
       *
+      * \warning It is recommended to report the audio delay via
+      * aout_TimingReport(). In that case, time_get should not be implemented.
+      *
       * \param delay pointer to the delay until the next sample to be written
       *              to the playback buffer is rendered [OUT]
       * \return 0 on success, non-zero on failure or lack of data
@@ -297,9 +300,23 @@ struct audio_output
     const struct vlc_audio_output_events *events;
 };
 
-static inline int aout_TimeGet(audio_output_t *aout, vlc_tick_t *delay)
+/**
+ * Report a new timing point
+ *
+ * system_ts doesn't have to be close to vlc_tick_now(). Any valid { system_ts,
+ * audio_ts } points in the past are sufficient to update the clock.
+ *
+ * \note audio_ts starts at 0 and should not take the block PTS into account.
+ *
+ * It is important to report the first point as soon as possible (and the
+ * following points if the audio delay take some time to be stabilized). Once
+ * the audio is stabilized, it is recommended to report timing points every few
+ * seconds.
+ */
+static inline void aout_TimingReport(audio_output_t *aout, vlc_tick_t system_ts,
+                                     vlc_tick_t audio_ts)
 {
-    return aout->time_get(aout, delay);
+    return aout->events->timing_report(aout, system_ts, audio_ts);
 }
 
 /**
@@ -369,16 +386,6 @@ static inline void aout_RestartRequest(audio_output_t *aout, unsigned mode)
     aout->events->restart_request(aout, mode);
 }
 
-/**
- * Default implementation for audio_output_t.time_get
- */
-static inline int aout_TimeGetDefault(audio_output_t *aout,
-                                      vlc_tick_t *restrict delay)
-{
-    (void) aout; (void) delay;
-    return -1;
-}
-
 /**
  * Default implementation for audio_output_t.pause
  *


=====================================
modules/audio_output/amem.c
=====================================
@@ -374,7 +374,7 @@ static int Open (vlc_object_t *obj)
     aout->sys = sys;
     aout->start = Start;
     aout->stop = Stop;
-    aout->time_get = aout_TimeGetDefault;
+    aout->time_get = NULL;
     aout->play = Play;
     aout->pause = Pause;
     aout->flush = Flush;


=====================================
modules/audio_output/file.c
=====================================
@@ -161,7 +161,7 @@ static int Start( audio_output_t *p_aout, audio_sample_format_t *restrict fmt )
         return VLC_EGENERIC;
     }
 
-    p_aout->time_get = aout_TimeGetDefault;
+    p_aout->time_get = NULL;
     p_aout->play = Play;
     p_aout->pause = Pause;
     p_aout->flush = Flush;


=====================================
modules/audio_output/pulse.c
=====================================
@@ -70,6 +70,7 @@ typedef struct
     bool draining;
     pa_cvolume cvolume; /**< actual sink input volume */
     vlc_tick_t last_date; /**< Play system timestamp of last buffer */
+    pa_usec_t flush_rt;
 
     pa_volume_t volume_force; /**< Forced volume (stream must be NULL) */
     pa_stream_flags_t flags_force; /**< Forced flags (stream must be NULL) */
@@ -102,6 +103,12 @@ static void drain_trigger_cb(pa_mainloop_api *api, pa_time_event *e,
     (void) api; (void) e; (void) tv;
 }
 
+static void stream_wait_cb(pa_stream *s, int success, void *userdata)
+{
+    (void) s; (void) success;
+    pa_threaded_mainloop_signal(userdata, 0);
+}
+
 static int TriggerDrain(audio_output_t *aout)
 {
     aout_sys_t *sys = aout->sys;
@@ -227,9 +234,13 @@ static void stream_stop(pa_stream *s, audio_output_t *aout)
         sys->trigger = NULL;
     }
 
-    op = pa_stream_cork(s, 1, NULL, NULL);
+    op = pa_stream_cork(s, 1, stream_wait_cb, sys->mainloop);
     if (op != NULL)
+    {
+        while (pa_operation_get_state(op) == PA_OPERATION_RUNNING)
+            pa_threaded_mainloop_wait(sys->mainloop);
         pa_operation_unref(op);
+    }
 }
 
 static void stream_trigger_cb(pa_mainloop_api *api, pa_time_event *e,
@@ -295,8 +306,35 @@ static void stream_latency_cb(pa_stream *s, void *userdata)
             TriggerDrain(aout);
         return; /* nothing to do if buffers are (still) empty */
     }
+
     if (pa_stream_is_corked(s) > 0)
         stream_start(s, aout, sys->last_date);
+
+    if (pa_stream_is_corked(s) == 0)
+    {
+        pa_usec_t rt;
+        if (pa_stream_get_time(s, &rt) == 0 && rt > 0)
+        {
+            if (likely(rt >= sys->flush_rt))
+            {
+                rt -= sys->flush_rt;
+                aout_TimingReport(aout, vlc_tick_now(), rt);
+            }
+#ifndef NDEBUG
+            else
+            {
+                /* The time returned by pa_stream_get_time() might be smaller
+                 * than flush_rt just after a flush (depending on
+                 * transport_usec, sink_usec), but the current read index
+                 * should always be superior or equal. */
+                const pa_sample_spec *ss = pa_stream_get_sample_spec(s);
+                const pa_timing_info *ti = pa_stream_get_timing_info(s);
+                if (ti != NULL && !ti->read_index_corrupt)
+                    assert(pa_bytes_to_usec(ti->read_index, ss) >= sys->flush_rt);
+            }
+#endif
+        }
+    }
 }
 
 
@@ -474,26 +512,6 @@ static void context_cb(pa_context *ctx, pa_subscription_event_type_t type,
 
 /*** VLC audio output callbacks ***/
 
-static int TimeGet(audio_output_t *aout, vlc_tick_t *restrict delay)
-{
-    aout_sys_t *sys = aout->sys;
-    pa_stream *s = sys->stream;
-    int ret = -1;
-
-    pa_threaded_mainloop_lock(sys->mainloop);
-    if (pa_stream_is_corked(s) <= 0)
-    {   /* latency is relevant only if not corked */
-        vlc_tick_t delta = vlc_pa_get_latency(aout, sys->context, s);
-        if (delta != VLC_TICK_INVALID)
-        {
-            *delay = delta;
-            ret = 0;
-        }
-    }
-    pa_threaded_mainloop_unlock(sys->mainloop);
-    return ret;
-}
-
 static void data_free(void *data)
 {
     block_Release(data);
@@ -517,7 +535,13 @@ static void Play(audio_output_t *aout, block_t *block, vlc_tick_t date)
     sys->last_date = date;
 
     if (pa_stream_is_corked(s) > 0)
-        stream_start(s, aout, date);
+    {
+        /* Trigger a latency update, that will update the stream_start timer
+         * using the last date. */
+        pa_operation *op = pa_stream_update_timing_info(s, NULL, NULL);
+        if (op != NULL)
+            pa_operation_unref(op);
+    }
 
 #if 0 /* Fault injector to test underrun recovery */
     static volatile unsigned u = 0;
@@ -580,8 +604,14 @@ static void Flush(audio_output_t *aout)
     if (op != NULL)
         pa_operation_unref(op);
     sys->last_date = VLC_TICK_INVALID;
+
     stream_stop(s, aout);
 
+    const pa_sample_spec *ss = pa_stream_get_sample_spec(s);
+    const pa_timing_info *ti = pa_stream_get_timing_info(s);
+    if (ti != NULL && !ti->read_index_corrupt)
+        sys->flush_rt = pa_bytes_to_usec(ti->read_index, ss);
+
     pa_threaded_mainloop_unlock(sys->mainloop);
 }
 
@@ -615,11 +645,18 @@ static void Drain(audio_output_t *aout)
     else
     {
         sys->last_date = VLC_TICK_INVALID;
+        sys->flush_rt = 0;
 
         /* XXX: Loosy drain emulation.
          * See #18141: drain callback is never received */
         sys->draining = true;
-        TriggerDrain(aout);
+
+        /* Trigger a latency update, that will update the drain timer */
+        op = pa_stream_update_timing_info(s, NULL, NULL);
+        if (op != NULL)
+            pa_operation_unref(op);
+        else
+            aout_DrainedReport(aout);
     }
 
     pa_threaded_mainloop_unlock(sys->mainloop);
@@ -826,7 +863,6 @@ static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
     /* Stream parameters */
     pa_stream_flags_t flags = sys->flags_force
                             | PA_STREAM_START_CORKED
-                            | PA_STREAM_INTERPOLATE_TIMING
                             | PA_STREAM_NOT_MONOTONIC
                             | PA_STREAM_AUTO_TIMING_UPDATE
                             | PA_STREAM_FIX_RATE;
@@ -852,6 +888,7 @@ static int Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
     sys->draining = false;
     pa_cvolume_init(&sys->cvolume);
     sys->last_date = VLC_TICK_INVALID;
+    sys->flush_rt = 0;
 
     pa_format_info *formatv = pa_format_info_new();
     formatv->encoding = encoding;
@@ -1068,7 +1105,7 @@ static int Open(vlc_object_t *obj)
     aout->sys = sys;
     aout->start = Start;
     aout->stop = Stop;
-    aout->time_get = TimeGet;
+    aout->time_get = NULL;
     aout->play = Play;
     aout->pause = Pause;
     aout->flush = Flush;


=====================================
src/audio_output/aout_internal.h
=====================================
@@ -139,6 +139,8 @@ struct vlc_aout_stream_cfg
     struct vlc_clock_t *clock;
     const char *str_id;
     const audio_replay_gain_t *replay_gain;
+    void (*notify_latency_cb)(void *data);
+    void *notify_latency_data;
 };
 
 vlc_aout_stream *vlc_aout_stream_New(audio_output_t *p_aout,
@@ -151,14 +153,19 @@ void vlc_aout_stream_ChangeRate(vlc_aout_stream *stream, float rate);
 void vlc_aout_stream_ChangeDelay(vlc_aout_stream *stream, vlc_tick_t delay);
 void vlc_aout_stream_Flush(vlc_aout_stream *stream);
 void vlc_aout_stream_Drain(vlc_aout_stream *stream);
+void vlc_aout_stream_UpdateLatency(vlc_aout_stream *stream);
 /* Contrary to other vlc_aout_stream_*() functions, this function can be called from
  * any threads */
 bool vlc_aout_stream_IsDrained(vlc_aout_stream *stream);
 /* Called from output.c */
+void vlc_aout_stream_NotifyTiming(vlc_aout_stream *stream, vlc_tick_t system_ts,
+                                  vlc_tick_t audio_ts);
 void vlc_aout_stream_NotifyDrained(vlc_aout_stream *stream);
 void vlc_aout_stream_NotifyGain(vlc_aout_stream *stream, float gain);
 
 void vlc_aout_stream_RequestRestart(vlc_aout_stream *stream, unsigned);
+void vlc_aout_stream_RequestRetiming(vlc_aout_stream *stream, vlc_tick_t system_ts,
+                                     vlc_tick_t audio_ts);
 
 void aout_InputRequestRestart(audio_output_t *aout);
 


=====================================
src/audio_output/dec.c
=====================================
@@ -39,6 +39,13 @@
 #include "clock/clock.h"
 #include "libvlc.h"
 
+#define MAX_TIMING_POINT 16
+struct timing_point
+{
+    vlc_tick_t system_ts;
+    vlc_tick_t audio_ts;
+};
+
 struct vlc_aout_stream
 {
     aout_instance_t *instance;
@@ -61,6 +68,29 @@ struct vlc_aout_stream
     } sync;
     vlc_tick_t original_pts;
 
+    struct
+    {
+        vlc_mutex_t lock; /* Guard data, count and head */
+
+        /* Circular array */
+        struct timing_point data[MAX_TIMING_POINT];
+        /* Number of points in the array */
+        size_t count;
+        /* Index of the next point to write */
+        size_t head;
+
+        bool running;
+
+        vlc_tick_t first_pts;
+        vlc_tick_t last_pts; /* Used for stream_TimeGet() emulation */
+
+        struct timing_point rate_point;
+        float rate;
+
+        void (*notify_latency_cb)(void *data);
+        void *notify_latency_data;
+    } timing_points;
+
     const char *str_id;
 
     /* Original input format and profile, won't change for the lifetime of a
@@ -99,6 +129,43 @@ static inline struct vlc_tracer *aout_stream_tracer(vlc_aout_stream *stream)
         vlc_object_get_tracer(VLC_OBJECT(aout_stream_aout(stream)));
 }
 
+static int stream_TimeGet(vlc_aout_stream *stream, vlc_tick_t *delay)
+{
+    audio_output_t *aout = aout_stream_aout(stream);
+
+    if (aout->time_get == NULL)
+    {
+        if (stream->timing_points.last_pts == VLC_TICK_INVALID)
+            return -1;
+
+        /* Interpolate the last updated point. */
+        vlc_tick_t system_now = vlc_tick_now();
+
+        vlc_tick_t play_date =
+            vlc_clock_ConvertToSystem(stream->sync.clock, system_now,
+                                      stream->timing_points.last_pts,
+                                      stream->sync.rate);
+        *delay = play_date - system_now;
+        return 0;
+    }
+    return aout->time_get(aout, delay);
+}
+
+static void stream_Discontinuity(vlc_aout_stream *stream)
+{
+    stream->sync.discontinuity = true;
+    stream->original_pts = VLC_TICK_INVALID;
+
+    vlc_mutex_lock(&stream->timing_points.lock);
+    stream->timing_points.head = stream->timing_points.count = 0;
+    vlc_mutex_unlock(&stream->timing_points.lock);
+
+    stream->timing_points.first_pts =
+    stream->timing_points.last_pts = VLC_TICK_INVALID;
+
+    stream->timing_points.running = false;
+}
+
 static void stream_Reset(vlc_aout_stream *stream)
 {
     aout_owner_t *owner = aout_stream_owner(stream);
@@ -130,12 +197,14 @@ static void stream_Reset(vlc_aout_stream *stream)
         }
     }
 
+    stream->timing_points.rate_point.audio_ts = VLC_TICK_INVALID;
+    stream->timing_points.rate = 1.0;
+
     atomic_store_explicit(&stream->drained, false, memory_order_relaxed);
     atomic_store_explicit(&stream->drain_deadline, VLC_TICK_INVALID,
                           memory_order_relaxed);
 
-    stream->sync.discontinuity = true;
-    stream->original_pts = VLC_TICK_INVALID;
+    stream_Discontinuity(stream);
 }
 
 /**
@@ -193,6 +262,25 @@ vlc_aout_stream * vlc_aout_stream_New(audio_output_t *p_aout,
     stream->sync.clock = cfg->clock;
     stream->str_id = cfg->str_id;
 
+    stream->timing_points.rate_point.audio_ts = VLC_TICK_INVALID;
+    stream->timing_points.rate = 1.f;
+
+    vlc_mutex_init(&stream->timing_points.lock);
+    stream->timing_points.notify_latency_cb = cfg->notify_latency_cb;
+    stream->timing_points.notify_latency_data = cfg->notify_latency_data;
+
+    stream->sync.rate = 1.f;
+    stream->sync.resamp_type = AOUT_RESAMPLING_NONE;
+    stream->sync.delay = stream->sync.request_delay = 0;
+    stream_Discontinuity(stream);
+
+    atomic_init (&stream->buffers_lost, 0);
+    atomic_init (&stream->buffers_played, 0);
+    atomic_store_explicit(&owner->vp.update, true, memory_order_relaxed);
+
+    atomic_init(&stream->drained, false);
+    atomic_init(&stream->drain_deadline, VLC_TICK_INVALID);
+
     stream->filters = NULL;
     stream->filters_cfg = AOUT_FILTERS_CFG_INIT;
     if (aout_OutputNew(p_aout, stream, &stream->mixer_format, stream->input_profile,
@@ -224,19 +312,6 @@ error:
         }
     }
 
-    stream->sync.rate = 1.f;
-    stream->sync.resamp_type = AOUT_RESAMPLING_NONE;
-    stream->sync.discontinuity = true;
-    stream->sync.delay = stream->sync.request_delay = 0;
-    stream->original_pts = VLC_TICK_INVALID;
-
-    atomic_init (&stream->buffers_lost, 0);
-    atomic_init (&stream->buffers_played, 0);
-    atomic_store_explicit(&owner->vp.update, true, memory_order_relaxed);
-
-    atomic_init(&stream->drained, false);
-    atomic_init(&stream->drain_deadline, VLC_TICK_INVALID);
-
     return stream;
 }
 
@@ -258,6 +333,7 @@ void vlc_aout_stream_Delete (vlc_aout_stream *stream)
     }
     if (stream->volume != NULL)
         aout_volume_Delete(stream->volume);
+
     free(stream);
 }
 
@@ -382,15 +458,13 @@ static void stream_Silence (vlc_aout_stream *stream, vlc_tick_t length, vlc_tick
     aout->play(aout, block, system_pts);
 }
 
-static void stream_RequestRetiming(vlc_aout_stream *stream, vlc_tick_t system_ts,
-                                   vlc_tick_t audio_ts)
+static void stream_HandleDrift(vlc_aout_stream *stream, vlc_tick_t drift,
+                               vlc_tick_t audio_ts)
 {
     aout_owner_t *owner = aout_stream_owner(stream);
     audio_output_t *aout = aout_stream_aout(stream);
 
     float rate = stream->sync.rate;
-    vlc_tick_t drift =
-        vlc_clock_Update(stream->sync.clock, system_ts, audio_ts, rate);
 
     if (unlikely(drift == VLC_TICK_MAX) || owner->bitexact)
         return; /* cf. VLC_TICK_MAX comment in vlc_aout_stream_Play() */
@@ -527,9 +601,8 @@ static void stream_Synchronize(vlc_aout_stream *stream, vlc_tick_t system_now,
      *    pts = vlc_tick_now() + delay
      */
     vlc_tick_t delay;
-    audio_output_t *aout = aout_stream_aout(stream);
 
-    if (aout_TimeGet(aout, &delay) != 0)
+    if (stream_TimeGet(stream, &delay) != 0)
         return; /* nothing can be done if timing is unknown */
 
     if (stream->sync.discontinuity)
@@ -537,7 +610,7 @@ static void stream_Synchronize(vlc_aout_stream *stream, vlc_tick_t system_now,
         /* Chicken-egg situation for most aout modules that can't be started
          * deferred (all except PulseAudio). These modules will start to play
          * data immediately and ignore the given play_date (that take the clock
-         * jitter into account). We don't want to let stream_RequestRetiming()
+         * jitter into account). We don't want to let stream_HandleDrift()
          * handle the first silence (from the "Early audio output" case) since
          * this function will first update the clock without taking the jitter
          * into account. Therefore, we manually insert silence that correspond
@@ -550,12 +623,113 @@ static void stream_Synchronize(vlc_aout_stream *stream, vlc_tick_t system_now,
         if (jitter > 0)
         {
             stream_Silence(stream, jitter, dec_pts - delay);
-            if (aout_TimeGet(aout, &delay) != 0)
+            if (stream_TimeGet(stream, &delay) != 0)
                 return;
         }
     }
 
-    stream_RequestRetiming(stream, system_now + delay, dec_pts);
+    vlc_tick_t drift = vlc_clock_Update(stream->sync.clock, system_now + delay,
+                                        dec_pts, stream->sync.rate);
+
+    stream_HandleDrift(stream, drift, dec_pts);
+}
+
+void vlc_aout_stream_NotifyTiming(vlc_aout_stream *stream, vlc_tick_t system_ts,
+                                  vlc_tick_t audio_ts)
+{
+    /* This function might be called from high priority audio threads (so, no
+     * mutexes, allocation, IO, debug, wait...). That is why we use a circular
+     * buffer of points. The vlc_aout_stream user will read these points and
+     * update the clock from vlc_aout_stream_Play() and
+     * vlc_aout_stream_UpdateLatency(). */
+
+    /* VLC mutexes use atomic and the reader will only do very fast
+     * operations (copy of the timing_point data). */
+    vlc_mutex_lock(&stream->timing_points.lock);
+
+    size_t write_idx;
+    if (stream->timing_points.count == MAX_TIMING_POINT)
+    {
+        write_idx = stream->timing_points.head;
+        stream->timing_points.head = (stream->timing_points.head + 1)
+                                   % MAX_TIMING_POINT;
+    }
+    else
+        write_idx = stream->timing_points.count++;
+
+    struct timing_point *p = &stream->timing_points.data[write_idx];
+    p->system_ts = system_ts;
+    p->audio_ts = audio_ts;
+
+    vlc_mutex_unlock(&stream->timing_points.lock);
+
+    if (stream->timing_points.notify_latency_cb != NULL)
+        stream->timing_points.notify_latency_cb(
+                stream->timing_points.notify_latency_data);
+}
+
+static vlc_tick_t
+stream_ReadTimingPoints(vlc_aout_stream *stream)
+{
+    struct timing_point points[MAX_TIMING_POINT];
+    size_t count = 0;
+
+    vlc_mutex_lock(&stream->timing_points.lock);
+    assert(stream->timing_points.count <= MAX_TIMING_POINT
+        && stream->timing_points.head < MAX_TIMING_POINT);
+
+    size_t initial_read = stream->timing_points.head;
+    for (size_t read = stream->timing_points.head;
+         count < stream->timing_points.count; ++read, ++count)
+    {
+        points[count] = stream->timing_points.data[read % MAX_TIMING_POINT];
+        points[count].audio_ts += stream->timing_points.first_pts;
+    }
+
+    stream->timing_points.count = stream->timing_points.head = 0;
+    vlc_mutex_unlock(&stream->timing_points.lock);
+
+    if (count == 0)
+        return 0;
+
+    if (initial_read > 0)
+    {
+        /* This is not critical to miss timing points, the more important is
+         * to get the last ones. Log it anyway since it can help identifying
+         * buggy audio outputs spamming timing points. */
+
+        audio_output_t *aout = aout_stream_aout(stream);
+        msg_Dbg(aout, "Missed %zu timing points", initial_read);
+    }
+    vlc_tick_t drift = 0; /* Use only the last updated drift */
+
+    for (size_t i = 0; i < count; ++i)
+    {
+        struct timing_point *tp = &points[i];
+        struct timing_point *rp = &stream->timing_points.rate_point;
+
+        if (rp->audio_ts != VLC_TICK_INVALID)
+        {
+            /* Drop timing updates that comes before the rate change */
+            if (tp->system_ts < rp->system_ts)
+                continue;
+
+            /* Fix the audio timestamp with the rate */
+            tp->audio_ts = rp->audio_ts + (tp->system_ts - rp->system_ts)
+                         * stream->timing_points.rate;
+        }
+
+        drift = vlc_clock_Update(stream->sync.clock, points[i].system_ts,
+                                 points[i].audio_ts, stream->timing_points.rate);
+    }
+
+    return drift;
+}
+
+void vlc_aout_stream_UpdateLatency(vlc_aout_stream *stream)
+{
+    if (stream->timing_points.running)
+        stream_ReadTimingPoints(stream);
 }
 
 /*****************************************************************************
@@ -576,10 +750,7 @@ int vlc_aout_stream_Play(vlc_aout_stream *stream, block_t *block)
         goto drop; /* Pipeline is unrecoverably broken :-( */
 
     if (block->i_flags & BLOCK_FLAG_DISCONTINUITY)
-    {
-        stream->sync.discontinuity = true;
-        stream->original_pts = VLC_TICK_INVALID;
-    }
+        stream_Discontinuity(stream);
 
     if (stream->original_pts == VLC_TICK_INVALID)
     {
@@ -625,7 +796,13 @@ int vlc_aout_stream_Play(vlc_aout_stream *stream, block_t *block)
 
     /* Drift correction */
     vlc_tick_t system_now = vlc_tick_now();
-    stream_Synchronize(stream, system_now, original_pts);
+    if (aout->time_get != NULL)
+        stream_Synchronize(stream, system_now, original_pts);
+    else
+    {
+        vlc_tick_t drift = stream_ReadTimingPoints(stream);
+        stream_HandleDrift(stream, drift, original_pts);
+    }
 
     vlc_tick_t play_date =
         vlc_clock_ConvertToSystem(stream->sync.clock, system_now, original_pts,
@@ -640,15 +817,46 @@ int vlc_aout_stream_Play(vlc_aout_stream *stream, block_t *block)
 
     vlc_audio_meter_Process(&owner->meter, block, play_date);
 
+    if (!stream->timing_points.running)
+    {
+        stream->timing_points.running = true;
+
+        /* Assert that no timing points are updated between flush and play */
+#ifndef NDEBUG
+        vlc_mutex_lock(&stream->timing_points.lock);
+        assert(stream->timing_points.count == 0);
+        vlc_mutex_unlock(&stream->timing_points.lock);
+#endif
+    }
+
+    if (aout->time_get == NULL
+     && stream->sync.rate != stream->timing_points.rate)
+    {
+        /* Save the first timing point seeing a rate change */
+        stream->timing_points.rate_point = (struct timing_point)  {
+            .system_ts = play_date,
+            .audio_ts = original_pts,
+        };
+        stream->timing_points.rate = stream->sync.rate;
+
+        /* Update the clock immediately with the new rate, instead of waiting
+         * for a timing update that could come too late (after 1second). */
+        vlc_clock_Update(stream->sync.clock, play_date,
+                         original_pts, stream->sync.rate);
+    }
+
     /* Output */
     stream->sync.discontinuity = false;
     aout->play(aout, block, play_date);
 
+    if (stream->timing_points.first_pts == VLC_TICK_INVALID)
+        stream->timing_points.first_pts = original_pts;
+    stream->timing_points.last_pts = original_pts;
+
     atomic_fetch_add_explicit(&stream->buffers_played, 1, memory_order_relaxed);
     return ret;
 drop:
-    stream->sync.discontinuity = true;
-    stream->original_pts = VLC_TICK_INVALID;
+    stream_Discontinuity(stream);
     block_Release (block);
     atomic_fetch_add_explicit(&stream->buffers_lost, 1, memory_order_relaxed);
     return ret;
@@ -678,6 +886,17 @@ void vlc_aout_stream_ChangePause(vlc_aout_stream *stream, bool paused, vlc_tick_
             aout->pause(aout, paused, date);
         else if (paused)
             aout->flush(aout);
+
+        /* Update the rate point after the pause */
+        if (aout->time_get == NULL && !paused
+         && stream->timing_points.rate_point.audio_ts != VLC_TICK_INVALID)
+        {
+            vlc_tick_t play_date =
+                vlc_clock_ConvertToSystem(stream->sync.clock, date,
+                                          stream->timing_points.rate_point.audio_ts,
+                                          stream->sync.rate);
+            stream->timing_points.rate_point.system_ts = play_date;
+        }
     }
 }
 
@@ -699,9 +918,9 @@ void vlc_aout_stream_Flush(vlc_aout_stream *stream)
     if (tracer != NULL)
         vlc_tracer_TraceEvent(tracer, "RENDER", stream->str_id, "flushed");
 
-    stream_Reset(stream);
     if (stream->mixer_format.i_format)
         aout->flush(aout);
+    stream_Reset(stream);
 }
 
 void vlc_aout_stream_NotifyGain(vlc_aout_stream *stream, float gain)
@@ -763,7 +982,7 @@ void vlc_aout_stream_Drain(vlc_aout_stream *stream)
         vlc_tick_t drain_deadline = vlc_tick_now();
 
         vlc_tick_t delay;
-        if (aout_TimeGet(aout, &delay) == 0)
+        if (stream_TimeGet(stream, &delay) == 0)
             drain_deadline += delay;
         /* else the deadline is now, and vlc_aout_stream_IsDrained() will
          * return true on the first call. */
@@ -776,6 +995,5 @@ void vlc_aout_stream_Drain(vlc_aout_stream *stream)
     if (stream->filters)
         aout_FiltersResetClock(stream->filters);
 
-    stream->sync.discontinuity = true;
-    stream->original_pts = VLC_TICK_INVALID;
+    stream_Discontinuity(stream);
 }


=====================================
src/audio_output/output.c
=====================================
@@ -63,6 +63,14 @@ static int var_CopyDevice (vlc_object_t *src, const char *name,
     return var_Set (dst, "audio-device", value);
 }
 
+static void aout_TimingNotify(audio_output_t *aout, vlc_tick_t system_ts,
+                              vlc_tick_t audio_ts)
+{
+    aout_owner_t *owner = aout_owner (aout);
+    assert(owner->main_stream);
+    vlc_aout_stream_NotifyTiming(owner->main_stream, system_ts, audio_ts);
+}
+
 static void aout_DrainedNotify(audio_output_t *aout)
 {
     aout_owner_t *owner = aout_owner (aout);
@@ -168,6 +176,7 @@ static int aout_GainNotify (audio_output_t *aout, float gain)
 }
 
 static const struct vlc_audio_output_events aout_events = {
+    aout_TimingNotify,
     aout_DrainedNotify,
     aout_VolumeNotify,
     aout_MuteNotify,
@@ -758,9 +767,10 @@ int aout_OutputNew(audio_output_t *aout, vlc_aout_stream *stream,
     for (size_t i = 0; formats[i] != 0 && ret != VLC_SUCCESS; ++i)
     {
         filter_fmt->i_format = fmt->i_format = formats[i];
+        owner->main_stream = stream;
         ret = aout->start(aout, fmt);
-        if (ret == 0)
-            owner->main_stream = stream;
+        if (ret != 0)
+            owner->main_stream = NULL;
     }
     vlc_mutex_unlock(&owner->lock);
     if (ret)
@@ -772,7 +782,7 @@ int aout_OutputNew(audio_output_t *aout, vlc_aout_stream *stream,
                       "failing back to linear format");
         return -1;
     }
-    assert(aout->flush && aout->play && aout->time_get && aout->pause);
+    assert(aout->flush && aout->play && aout->pause);
 
     /* Autoselect the headphones mode if available and if the user didn't
      * request any mode */


=====================================
src/clock/clock.c
=====================================
@@ -161,6 +161,12 @@ static vlc_tick_t vlc_clock_master_update(vlc_clock_t *clock,
         main_clock->offset =
             system_now - ((vlc_tick_t) (ts * main_clock->coeff / rate));
 
+        if (main_clock->tracer != NULL && clock->track_str_id)
+            vlc_tracer_Trace(main_clock->tracer, VLC_TRACE("type", "RENDER"),
+                             VLC_TRACE("id", clock->track_str_id),
+                             VLC_TRACE("offset", main_clock->offset),
+                             VLC_TRACE_END);
+
         main_clock->last = clock_point_Create(system_now, ts);
 
         main_clock->rate = rate;
@@ -605,7 +611,7 @@ 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)
 {
     /* The master has always the 0 priority */
-    vlc_clock_t *clock = vlc_clock_main_Create(main_clock, NULL, 0, NULL, NULL);
+    vlc_clock_t *clock = vlc_clock_main_Create(main_clock, "input", 0, NULL, NULL);
     if (!clock)
         return NULL;
 


=====================================
src/input/decoder.c
=====================================
@@ -339,6 +339,13 @@ static bool aout_replaygain_changed( const audio_replay_gain_t *a,
     return false;
 }
 
+static void aout_stream_NotifyLatency(void *data)
+{
+    /* Wake the DecoderThread in order to update the audio latency via
+     * vlc_aout_stream_UpdateLatency() */
+    vlc_fifo_Signal(data);
+}
+
 static int ModuleThread_UpdateAudioFormat( decoder_t *p_dec )
 {
     vlc_input_decoder_t *p_owner = dec_get_owner( p_dec );
@@ -401,7 +408,9 @@ static int ModuleThread_UpdateAudioFormat( decoder_t *p_dec )
                 .profile = p_dec->fmt_out.i_profile,
                 .clock = p_owner->p_clock,
                 .str_id = p_owner->psz_id,
-                .replay_gain = &p_dec->fmt_out.audio_replay_gain
+                .replay_gain = &p_dec->fmt_out.audio_replay_gain,
+                .notify_latency_cb = aout_stream_NotifyLatency,
+                .notify_latency_data = p_owner->p_fifo,
             };
             p_astream = vlc_aout_stream_New( p_aout, &cfg );
             if( p_astream == NULL )
@@ -1758,6 +1767,13 @@ static void *DecoderThread( void *p_data )
                 p_owner->b_idle = true;
                 vlc_cond_signal( &p_owner->wait_acknowledge );
                 vlc_fifo_Wait( p_owner->p_fifo );
+
+                /* Update the audio latency after a possible long wait in order
+                 * to update the audio clock as soon as possible. */
+                if( p_owner->dec.fmt_in.i_cat == AUDIO_ES &&
+                    p_owner->p_astream != NULL )
+                    vlc_aout_stream_UpdateLatency( p_owner->p_astream );
+
                 p_owner->b_idle = false;
                 continue;
             }



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/0718770a5c56a9b0ee36bc6621a08f367aa049a6...9eabb2821e83037efadb5b53f66d8316cc33ef28

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/0718770a5c56a9b0ee36bc6621a08f367aa049a6...9eabb2821e83037efadb5b53f66d8316cc33ef28
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