[vlc-devel] [PATCH V3 13/19] player: add the timer API

Thomas Guillem thomas at gllm.fr
Fri Sep 6 17:20:43 CEST 2019


Any interface or control modules could request a timer from the player. This
player timer is like the player event listener except that:

 - It is only used to receive time update points0:

 - The timer is not locked by the player lock. Indeed the player lock can be
   too "slow" (it can be recursive, it is used by the playlist, and is it held
   when sending all events). So it's not a good idea to hold this lock for
   every frame/sample updates.

 - The minimum delay between each updates can be configured: it avoids to flood
   the UI when playing a media file with very high fps or very low audio sample
   size.

The time updated is the output time, unlike the on_position_changed event that
use the input time. It can fixes a very big delay between the UI time widgets
and the outputted content (depending on the audio output module, this delay
could be close to 2seconds).

The vlc_player_timer_point struct is used by timer update callbacks. This
public struct hold all the informations to interpolate a time at a given date.
It could be done with the vlc_player_timer_point_Interpolate() helper. That
way, it is now possible to get the last player time without holding any locks.

There are two timer types:

 - vlc_player_AddTimer(): update are sent only when a frame or a sample is outputted. Users of
   this timer should take into account that the delay between each updates is
   not regular and can be up to 1seconds (depending of the input). In that
   case, they should use their own timer (from their mainloop) and use
   vlc_player_timer_point_Interpolate() to get the last time.

 - vlc_player_AddSmpteTimer(): send a SMPTE timecode each time a frame is
   rendered. This timer use a different callback struct and data struct:
   vlc_player_timer_smpte_timecode. It's not possible to interpolate it, the UI
   should update its widgets once it receive the new timecode update. This
   SMPTE timer handle NTSC 29.97 and 59.94 drop frames and is frame accurate.
---
 include/vlc_player.h | 178 +++++++++++++++++
 src/Makefile.am      |   1 +
 src/libvlccore.sym   |   4 +
 src/player/input.c   |  83 +++++++-
 src/player/player.c  |   7 +
 src/player/player.h  |  86 ++++++++
 src/player/timer.c   | 455 +++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 813 insertions(+), 1 deletion(-)
 create mode 100644 src/player/timer.c

diff --git a/include/vlc_player.h b/include/vlc_player.h
index bf79cdba6e..3001a390de 100644
--- a/include/vlc_player.h
+++ b/include/vlc_player.h
@@ -3047,6 +3047,184 @@ vlc_player_RemoveListener(vlc_player_t *player,
 
 /** @} vlc_player__events */
 
+/**
+ * @defgroup vlc_player__timer Player timer
+ * @{
+ */
+
+/**
+ * Player timer opaque structure.
+ */
+typedef struct vlc_player_timer_id vlc_player_timer_id;
+
+/**
+ * Player timer state
+ *
+ * @see vlc_player_timer_cbs.on_update
+ */
+enum vlc_player_timer_state
+{
+    /* Normal state, the player is playing */
+    VLC_PLAYER_TIMER_STATE_PLAYING,
+    /* The player is paused */
+    VLC_PLAYER_TIMER_STATE_PAUSED,
+    /* A discontinuity occurred, likely caused by seek from the user. */
+    VLC_PLAYER_TIMER_STATE_DISCONTINUITY,
+};
+
+/**
+ * Player timer point
+ *
+ * @see vlc_player_timer_cbs.on_update
+ */
+struct vlc_player_timer_point
+{
+    /** Position in the range [0.0f;1.0] */
+    float position;
+    /** Rate of the player */
+    double rate;
+    /** Valid time >= VLC_TICK_0 or VLC_TICK_INVALID, subtract this time with
+     * VLC_TICK_0 to get the original value. */
+    vlc_tick_t ts;
+    /** Valid length >= VLC_TICK_0 or VLC_TICK_INVALID */
+    vlc_tick_t length;
+    /** System date of this record (always valid), this date can be in the
+     * future. */
+    vlc_tick_t system_date;
+};
+
+/**
+ * Player smpte timecode
+ *
+ * @see vlc_player_timer_smpte_cbs
+ */
+struct vlc_player_timer_smpte_timecode
+{
+    /** Hours [0;n] */
+    unsigned hours;
+    /** Minutes [0;59] */
+    unsigned minutes;
+    /** Seconds [0;59] */
+    unsigned seconds;
+    /** Frame number [0;n] */
+    unsigned frames;
+    /** Maximum number of digits needed to display the frame number */
+    unsigned frame_resolution;
+    /** True if the source is NTSC 29.97fps or 59.94fps DF */
+    bool drop_frame;
+};
+
+/**
+ * Player timer callbacks
+ *
+ * @see vlc_player_AddTimer
+ */
+struct vlc_player_timer_cbs
+{
+    /**
+     * Called when the state or the time changed.
+     *
+     * Get notified when the time is updated by the input or output source. The
+     * input source is the 'demux' or the 'access_demux'. The output source are
+     * audio and video outputs: an update is received each time a video frame
+     * is displayed or an audio sample is written. The delay between each
+     * updates may depend on the input and source type (it can be every 5ms,
+     * 30ms, 1s or 10s...). The user of this timer may need to update the
+     * position at a higher frequency from its own mainloop via
+     * vlc_player_timer_point_Interpolate().
+     *
+     * @warning The player is not locked from this callback. It is forbidden
+     * to call any player functions from here.
+     *
+     * @param state PLAYING, PAUSED or DISCONTINUITY
+     * @param value always valid, the time corresponding to the state
+     * @param data opaque pointer set by vlc_player_AddTimer()
+     */
+    void (*on_update)(enum vlc_player_timer_state state,
+                      const struct vlc_player_timer_point *value, void *data);
+};
+
+/**
+ * Player smpte timer callbacks
+ *
+ * @see vlc_player_AddSmpteTimer
+ */
+struct vlc_player_timer_smpte_cbs
+{
+    /**
+     * Called when a new frame is displayed
+
+     * @warning The player is not locked from this callback. It is forbidden
+     * to call any player functions from here.
+     *
+     * @param tc always valid, the timecode corresponding to the frame just
+     * displayed
+     * @param data opaque pointer set by vlc_player_AddTimer()
+     */
+    void (*on_update)(const struct vlc_player_timer_smpte_timecode *tc,
+                      void *data);
+};
+
+/**
+ * Add a timer in order to get times updates
+ *
+ * @param player player instance (locked or not)
+ * @param min_period corresponds to the minimum period between each updates,
+ * use it to avoid flood from too many source updates, set it to
+ * VLC_TICK_INVALID to receive all updates.
+ * @param cbs pointer to a vlc_player_timer_cbs structure, the structure must
+ * be valid during the lifetime of the player
+ * @param cbs_data opaque pointer used by the callbacks
+ * @return a valid vlc_player_timer_id or NULL in case of memory allocation
+ * error
+ */
+VLC_API vlc_player_timer_id *
+vlc_player_AddTimer(vlc_player_t *player, vlc_tick_t min_period,
+                    const struct vlc_player_timer_cbs *cbs, void *data);
+
+/**
+ * Add a smpte timer in order to get accurate video frame updates
+ *
+ * @param player player instance (locked or not)
+ * @param cbs pointer to a vlc_player_timer_smpte_cbs structure, the structure must
+ * be valid during the lifetime of the player
+ * @param cbs_data opaque pointer used by the callbacks
+ * @return a valid vlc_player_timer_id or NULL in case of memory allocation
+ * error
+ */
+VLC_API vlc_player_timer_id *
+vlc_player_AddSmpteTimer(vlc_player_t *player,
+                         const struct vlc_player_timer_smpte_cbs *cbs,
+                         void *data);
+
+/**
+ * Remove a player timer
+ *
+ * @param player player instance (locked or not)
+ * @param timer timer created by vlc_player_AddTimer()
+ */
+VLC_API void
+vlc_player_RemoveTimer(vlc_player_t *player, vlc_player_timer_id *timer);
+
+/**
+ * Interpolate the last timer value to now
+ *
+ * @param point time update obtained via the vlc_player_timer_cbs.on_update()
+ * callback
+ * @param system_now current system date
+ * @param player_rate rate of the player
+ * @param out_ts pointer where to set the interpolated ts
+ * @param out_pos pointer where to set the interpolated position
+ * @return VLC_SUCCESS in case of success, an error in the interpolated ts is
+ * negative (could happen during the buffering step)
+ */
+VLC_API int
+vlc_player_timer_point_Interpolate(const struct vlc_player_timer_point *point,
+                                   vlc_tick_t system_now,
+                                   vlc_tick_t *out_ts, float *out_pos);
+
+/** @} vlc_player__timer */
+
 /** @} vlc_player */
 
 #endif
diff --git a/src/Makefile.am b/src/Makefile.am
index 9f0b6df13a..e91fff6a3f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -265,6 +265,7 @@ libvlccore_la_SOURCES = \
 	player/player.c \
 	player/player.h \
 	player/input.c \
+	player/timer.c \
 	player/track.c \
 	player/title.c \
 	player/aout.c \
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index f278fe6e9d..36c1cf3d4e 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -766,6 +766,8 @@ vlc_thumbnailer_Cancel
 vlc_thumbnailer_Release
 vlc_player_AddAssociatedMedia
 vlc_player_AddListener
+vlc_player_AddSmpteTimer
+vlc_player_AddTimer
 vlc_player_aout_AddListener
 vlc_player_aout_EnableFilter
 vlc_player_aout_GetVolume
@@ -824,6 +826,7 @@ vlc_player_Pause
 vlc_player_program_Delete
 vlc_player_program_Dup
 vlc_player_RemoveListener
+vlc_player_RemoveTimer
 vlc_player_RestartEsId
 vlc_player_Resume
 vlc_player_SeekByPos
@@ -860,6 +863,7 @@ vlc_player_SetTeletextTransparency
 vlc_player_SetTrackCategoryEnabled
 vlc_player_Start
 vlc_player_Stop
+vlc_player_timer_point_Interpolate
 vlc_player_title_list_GetAt
 vlc_player_title_list_GetCount
 vlc_player_title_list_Hold
diff --git a/src/player/input.c b/src/player/input.c
index d63b2ff59c..81b47844a5 100644
--- a/src/player/input.c
+++ b/src/player/input.c
@@ -60,12 +60,24 @@ vlc_player_input_HandleAtoBLoop(struct vlc_player_input *input, vlc_tick_t time,
 vlc_tick_t
 vlc_player_input_GetTime(struct vlc_player_input *input)
 {
+    vlc_player_t *player = input->player;
+    vlc_tick_t ts;
+
+    if (input == player->input
+     && vlc_player_GetTimerPoint(player, vlc_tick_now(), &ts, NULL) == 0)
+        return ts;
     return input->time;
 }
 
 float
 vlc_player_input_GetPos(struct vlc_player_input *input)
 {
+    vlc_player_t *player = input->player;
+    float pos;
+
+    if (input == player->input
+     && vlc_player_GetTimerPoint(player, vlc_tick_now(), NULL, &pos) == 0)
+        return pos;
     return input->position;
 }
 
@@ -172,6 +184,12 @@ vlc_player_input_HandleState(struct vlc_player_input *input,
             break;
         case VLC_PLAYER_STATE_STOPPING:
             input->started = false;
+
+            const vlc_tick_t system_now = vlc_tick_now();
+            vlc_player_UpdateTimerState(player, NULL,
+                                        VLC_PLAYER_TIMER_STATE_DISCONTINUITY,
+                                        system_now, system_now);
+
             if (input == player->input)
                 player->input = NULL;
 
@@ -183,8 +201,17 @@ vlc_player_input_HandleState(struct vlc_player_input *input,
             }
             send_event = !player->started;
             break;
-        case VLC_PLAYER_STATE_STARTED:
         case VLC_PLAYER_STATE_PLAYING:
+            assert(state_date != VLC_TICK_INVALID);
+            if (input->pause_date != VLC_TICK_INVALID)
+            {
+                vlc_player_UpdateTimerState(player, NULL,
+                                            VLC_PLAYER_TIMER_STATE_PLAYING,
+                                            input->pause_date, state_date);
+                input->pause_date = VLC_TICK_INVALID;
+            }
+            /* fallthrough */
+        case VLC_PLAYER_STATE_STARTED:
             if (player->started &&
                 player->global_state == VLC_PLAYER_STATE_PLAYING)
                 send_event = false;
@@ -192,6 +219,12 @@ vlc_player_input_HandleState(struct vlc_player_input *input,
 
         case VLC_PLAYER_STATE_PAUSED:
             assert(player->started && input->started);
+            assert(state_date != VLC_TICK_INVALID);
+            input->pause_date = state_date;
+
+            vlc_player_UpdateTimerState(player, NULL,
+                                        VLC_PLAYER_TIMER_STATE_PAUSED,
+                                        input->pause_date, state_date);
             break;
         default:
             vlc_assert_unreachable();
@@ -446,6 +479,7 @@ vlc_player_input_HandleEsEvent(struct vlc_player_input *input,
             trackpriv = vlc_player_track_vector_FindById(vec, ev->id, NULL);
             if (trackpriv)
             {
+                vlc_player_RemoveTimerSource(player, ev->id);
                 trackpriv->t.selected = false;
                 vlc_player_SendEvent(player, on_track_selection_changed,
                                      trackpriv->t.es_id, NULL);
@@ -573,6 +607,33 @@ input_thread_Events(input_thread_t *input_thread,
 
     assert(input_thread == input->thread);
 
+    /* No player lock for this event */
+    if (event->type == INPUT_EVENT_OUTPUT_CLOCK)
+    {
+        if (event->output_clock.system_ts != VLC_TICK_INVALID)
+        {
+            const struct vlc_player_timer_point point = {
+                .position = 0,
+                .rate = event->output_clock.rate,
+                .ts = event->output_clock.ts,
+                .length = VLC_TICK_INVALID,
+                .system_date = event->output_clock.system_ts,
+            };
+            vlc_player_UpdateTimer(player, event->output_clock.id,
+                                   event->output_clock.master, &point,
+                                   event->output_clock.frame_rate,
+                                   event->output_clock.frame_rate_base);
+        }
+        else
+        {
+            const vlc_tick_t system_now = vlc_tick_now();
+            vlc_player_UpdateTimerState(player, event->output_clock.id,
+                                        VLC_PLAYER_TIMER_STATE_DISCONTINUITY,
+                                        system_now, system_now);
+        }
+        return;
+    }
+
     vlc_mutex_lock(&player->lock);
 
     switch (event->type)
@@ -594,12 +655,18 @@ input_thread_Events(input_thread_t *input_thread,
             break;
         }
         case INPUT_EVENT_TIMES:
+        {
+            bool changed = false;
+            vlc_tick_t system_date = VLC_TICK_INVALID;
+
             if (event->times.ms != VLC_TICK_INVALID
              && (input->time != event->times.ms
               || input->position != event->times.percentage))
             {
                 input->time = event->times.ms;
                 input->position = event->times.percentage;
+                system_date = vlc_tick_now();
+                changed = true;
                 vlc_player_SendEvent(player, on_position_changed,
                                      input->time, input->position);
 
@@ -609,8 +676,21 @@ input_thread_Events(input_thread_t *input_thread,
             {
                 input->length = event->times.length;
                 vlc_player_SendEvent(player, on_length_changed, input->length);
+                changed = true;
+            }
+            if (changed)
+            {
+                const struct vlc_player_timer_point point = {
+                    .position = input->position,
+                    .rate = input->rate,
+                    .ts = input->time,
+                    .length = input->length,
+                    .system_date = system_date,
+                };
+                vlc_player_UpdateTimer(player, NULL, false, &point, 0, 0);
             }
             break;
+        }
         case INPUT_EVENT_PROGRAM:
             vlc_player_input_HandleProgramEvent(input, &event->program);
             break;
@@ -694,6 +774,7 @@ vlc_player_input_New(vlc_player_t *player, input_item_t *item)
     input->rate = 1.f;
     input->capabilities = 0;
     input->length = input->time = VLC_TICK_INVALID;
+    input->pause_date = VLC_TICK_INVALID;
     input->position = 0.f;
 
     input->recording = false;
diff --git a/src/player/player.c b/src/player/player.c
index 5a1ccc7d03..15f17ef22a 100644
--- a/src/player/player.c
+++ b/src/player/player.c
@@ -897,6 +897,9 @@ vlc_player_Lock(vlc_player_t *player)
      * vlc_player_aout_cbs documentation */
     assert(!vlc_mutex_marked(&player->vout_listeners_lock));
     assert(!vlc_mutex_marked(&player->aout_listeners_lock));
+    /* The timer lock should not be held (possible lock-order-inversion), cf.
+     * vlc_player_timer_cbs.on_update documentation */
+    assert(!vlc_mutex_marked(&player->timer.lock));
 
     vlc_mutex_lock(&player->lock);
 }
@@ -1835,6 +1838,8 @@ vlc_player_Delete(vlc_player_t *player)
 
     vlc_player_DestroyLocks(player);
 
+    vlc_player_DestroyTimer(player);
+
     vlc_player_aout_DelCallbacks(player);
     var_DelCallback(player, "corks", vlc_player_CorkCallback, NULL);
 
@@ -1936,11 +1941,13 @@ vlc_player_New(vlc_object_t *parent, enum vlc_player_lock_type lock_type,
 
     player->deleting = false;
     vlc_player_InitLocks(player, lock_type);
+    vlc_player_InitTimer(player);
 
     if (vlc_clone(&player->destructor.thread, vlc_player_destructor_Thread,
                   player, VLC_THREAD_PRIORITY_LOW) != 0)
     {
         vlc_player_DestroyLocks(player);
+        vlc_player_DestroyTimer(player);
         goto error;
     }
 
diff --git a/src/player/player.h b/src/player/player.h
index 024b425ba2..c87878bab0 100644
--- a/src/player/player.h
+++ b/src/player/player.h
@@ -65,6 +65,8 @@ struct vlc_player_input
     float position;
     vlc_tick_t time;
 
+    vlc_tick_t pause_date;
+
     bool recording;
 
     float signal_quality;
@@ -121,6 +123,57 @@ struct vlc_player_aout_listener_id
     struct vlc_list node;
 };
 
+enum vlc_player_timer_source_type
+{
+    VLC_PLAYER_TIMER_TYPE_BEST,
+    VLC_PLAYER_TIMER_TYPE_SMPTE,
+    VLC_PLAYER_TIMER_TYPE_COUNT
+};
+
+struct vlc_player_timer_id
+{
+    vlc_tick_t period;
+    vlc_tick_t last_update_date;
+
+    union
+    {
+        const struct vlc_player_timer_cbs *cbs;
+        const struct vlc_player_timer_smpte_cbs *smpte_cbs;
+    };
+    void *data;
+
+    struct vlc_list node;
+};
+
+struct vlc_player_timer_source
+{
+    struct vlc_list listeners; /* list of struct vlc_player_timer_id */
+    vlc_es_id_t *es; /* weak reference */
+    struct vlc_player_timer_point point;
+    union
+    {
+        struct {
+            long last_ts;
+            unsigned frame_rate;
+            unsigned frame_rate_base;
+            unsigned frame_resolution;
+            unsigned df_fps;
+            int df;
+            int frames_per_10mins;
+        } smpte;
+    };
+};
+
+struct vlc_player_timer
+{
+    vlc_mutex_t lock;
+    vlc_tick_t input_length;
+    float input_position;
+    struct vlc_player_timer_source sources[VLC_PLAYER_TIMER_TYPE_COUNT];
+#define best_source sources[VLC_PLAYER_TIMER_TYPE_BEST]
+#define smpte_source sources[VLC_PLAYER_TIMER_TYPE_SMPTE]
+};
+
 struct vlc_player_t
 {
     struct vlc_object_t obj;
@@ -167,6 +220,8 @@ struct vlc_player_t
         struct vlc_list stopping_inputs;
         struct vlc_list joinable_inputs;
     } destructor;
+
+    struct vlc_player_timer timer;
 };
 
 #ifndef NDEBUG
@@ -316,6 +371,37 @@ void
 vlc_player_input_HandleState(struct vlc_player_input *, enum vlc_player_state,
                              vlc_tick_t state_date);
 
+struct vlc_player_timer_point
+vlc_player_input_GetTimerValue(struct vlc_player_input *input);
+
+/*
+ * player_timer.c
+*/
+
+void
+vlc_player_InitTimer(vlc_player_t *player);
+
+void
+vlc_player_DestroyTimer(vlc_player_t *player);
+
+void
+vlc_player_UpdateTimerState(vlc_player_t *player, vlc_es_id_t *es_source,
+                            enum vlc_player_timer_state state,
+                            vlc_tick_t interpolate_date, vlc_tick_t system_date);
+
+void
+vlc_player_UpdateTimer(vlc_player_t *player, vlc_es_id_t *es_source,
+                       bool es_source_is_master,
+                       const struct vlc_player_timer_point *point,
+                       unsigned frame_rate, unsigned frame_rate_base);
+
+void
+vlc_player_RemoveTimerSource(vlc_player_t *player, vlc_es_id_t *es_source);
+
+int
+vlc_player_GetTimerPoint(vlc_player_t *player, vlc_tick_t system_now,
+                         vlc_tick_t *out_ts, float *out_pos);
+
 /*
  * player_vout.c
  */
diff --git a/src/player/timer.c b/src/player/timer.c
new file mode 100644
index 0000000000..cc5826a38d
--- /dev/null
+++ b/src/player/timer.c
@@ -0,0 +1,455 @@
+/*****************************************************************************
+ * player.c: Player interface
+ *****************************************************************************
+ * Copyright © 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <limits.h>
+
+#include "player.h"
+
+static void
+vlc_player_SendTimerSourceUpdates(vlc_player_t *player,
+                                  struct vlc_player_timer_source *source,
+                                  bool force_update,
+                                  enum vlc_player_timer_state state,
+                                  const struct vlc_player_timer_point *point)
+{
+    (void) player;
+    vlc_player_timer_id *timer;
+
+    vlc_list_foreach(timer, &source->listeners, node)
+    {
+        /* Respect refresh delay of the timer */
+        if (!force_update && timer->period != VLC_TICK_INVALID
+         && timer->last_update_date != VLC_TICK_INVALID
+         && point->system_date - timer->last_update_date < timer->period)
+            continue;
+        timer->cbs->on_update(state, point, timer->data);
+        timer->last_update_date = point->system_date;
+    }
+}
+
+static void
+vlc_player_SendSmpteTimerSourceUpdates(vlc_player_t *player,
+                                       struct vlc_player_timer_source *source,
+                                       const struct vlc_player_timer_point *point)
+{
+    (void) player;
+    vlc_player_timer_id *timer;
+
+    if (point->ts == source->smpte.last_ts)
+        return;
+
+    source->smpte.last_ts = point->ts;
+
+    struct vlc_player_timer_smpte_timecode tc;
+    long framenum;
+    unsigned frame_rate;
+    unsigned frame_rate_base;
+
+    if (source->smpte.df > 0)
+    {
+        /* Use the exact SMPTE framerate that can be different from the input
+         * source (at demuxer/decoder level) */
+        assert(source->smpte.df_fps == 30 || source->smpte.df_fps == 60);
+        frame_rate = source->smpte.df_fps * 1000;
+        frame_rate_base = 1001;
+
+        framenum = round((point->ts - VLC_TICK_0) * frame_rate
+                         / (double) frame_rate_base / VLC_TICK_FROM_SEC(1));
+
+        /* Drop 2 or 4 frames every minutes except every 10 minutes in order to
+         * make one hour of timecode match one hour on the clock. */
+        ldiv_t res;
+        res = ldiv(framenum, source->smpte.frames_per_10mins);
+
+        framenum += (9 * source->smpte.df * res.quot)
+                  + (source->smpte.df * ((res.rem - source->smpte.df)
+                     / (source->smpte.frames_per_10mins / 10)));
+
+        tc.drop_frame = true;
+        frame_rate = source->smpte.df_fps;
+        frame_rate_base = 1;
+    }
+    else
+    {
+        frame_rate = source->smpte.frame_rate;
+        frame_rate_base = source->smpte.frame_rate_base;
+
+        framenum = round((point->ts - VLC_TICK_0) * frame_rate
+                         / (double) frame_rate_base / VLC_TICK_FROM_SEC(1));
+
+        tc.drop_frame = false;
+    }
+
+    tc.frames = framenum % (frame_rate / frame_rate_base);
+    tc.seconds = (framenum * frame_rate_base / frame_rate) % 60;
+    tc.minutes = (framenum * frame_rate_base / frame_rate / 60) % 60;
+    tc.hours = framenum * frame_rate_base / frame_rate / 3600;
+
+    tc.frame_resolution = source->smpte.frame_resolution;
+
+    vlc_list_foreach(timer, &source->listeners, node)
+        timer->smpte_cbs->on_update(&tc, timer->data);
+}
+
+static void
+vlc_player_UpdateSmpteTimerFPS(vlc_player_t *player,
+                               struct vlc_player_timer_source *source,
+                               unsigned frame_rate, unsigned frame_rate_base)
+{
+    (void) player;
+    source->smpte.last_ts = VLC_TICK_INVALID;
+    source->smpte.frame_rate = frame_rate;
+    source->smpte.frame_rate_base = frame_rate_base;
+
+    /* Calculate everything that will be needed to create smpte timecodes */
+    source->smpte.frame_resolution = 0;
+
+    unsigned max_frames = frame_rate / frame_rate_base;
+
+    if (max_frames == 29 && (100 * frame_rate / frame_rate_base) == 2997)
+    {
+        /* SMPTE Timecode: 29.97 fps DF */
+        source->smpte.df = 2;
+        source->smpte.df_fps = 30;
+        source->smpte.frames_per_10mins = 17982; /* 29.97 * 60 * 10 */
+    }
+    else if (max_frames == 59 && (100 * frame_rate / frame_rate_base) == 5994)
+    {
+        /* SMPTE Timecode: 59.94 fps DF */
+        source->smpte.df = 4;
+        source->smpte.df_fps = 60;
+        source->smpte.frames_per_10mins = 35964; /* 59.94 * 60 * 10 */
+    }
+    else
+        source->smpte.df = 0;
+
+    while (max_frames != 0)
+    {
+        max_frames /= 10;
+        source->smpte.frame_resolution++;
+    }
+}
+
+void
+vlc_player_UpdateTimerState(vlc_player_t *player, vlc_es_id_t *es_source,
+                            enum vlc_player_timer_state state,
+                            vlc_tick_t interpolate_date, vlc_tick_t system_date)
+{
+    vlc_mutex_lock(&player->timer.lock);
+
+    if (state == VLC_PLAYER_TIMER_STATE_DISCONTINUITY)
+    {
+        /* Discontinuity is signalled by all output clocks and the input.
+         * discard the event if it was already signalled or not on the good
+         * es_source. */
+        bool signal_discontinuity = false;
+        for (size_t i = 0; i < VLC_PLAYER_TIMER_TYPE_COUNT; ++i)
+        {
+            struct vlc_player_timer_source *source = &player->timer.sources[i];
+            if (source->es == es_source
+             && source->point.system_date != VLC_TICK_INVALID)
+            {
+                source->point.system_date = VLC_TICK_INVALID;
+                /* signal discontinuity only on best source */
+                if (i == VLC_PLAYER_TIMER_TYPE_BEST)
+                    signal_discontinuity = true;
+            }
+        }
+        if (!signal_discontinuity)
+        {
+            vlc_mutex_unlock(&player->timer.lock);
+            return;
+        }
+    }
+
+    struct vlc_player_timer_source *source = &player->timer.best_source;
+    if (!vlc_list_is_empty(&source->listeners))
+    {
+        /* Update the new state and interpolate the last point to the
+         * interpolate_date (likely now) */
+        vlc_tick_t new_ts = VLC_TICK_0; /* Fallback to 0 in case of failure */
+        float new_pos = 0;
+        vlc_player_timer_point_Interpolate(&source->point, interpolate_date,
+                                           &new_ts, &new_pos);
+
+        const struct vlc_player_timer_point point =
+        {
+            .position = new_pos,
+            .rate = source->point.rate,
+            .ts = new_ts,
+            .length = source->point.length,
+            .system_date = system_date,
+        };
+        vlc_player_SendTimerSourceUpdates(player, source, true, state,
+                                          &point);
+    }
+    vlc_mutex_unlock(&player->timer.lock);
+}
+
+static void
+vlc_player_UpdateTimerSource(vlc_player_t *player,
+                             struct vlc_player_timer_source *source,
+                             double rate, vlc_tick_t ts, vlc_tick_t system_date,
+                             bool *force_update)
+{
+    assert(ts != VLC_TICK_INVALID && ts >= VLC_TICK_0);
+
+    *force_update = source->point.rate != rate
+                 || source->point.length != player->timer.input_length;
+
+    source->point.rate = rate;
+    source->point.ts = ts;
+    source->point.system_date = system_date;
+    source->point.length = player->timer.input_length;
+
+    if (source->point.length != VLC_TICK_INVALID)
+        source->point.position = source->point.ts
+                               / (double) source->point.length;
+    else
+        source->point.position = player->timer.input_position;
+}
+
+void
+vlc_player_UpdateTimer(vlc_player_t *player, vlc_es_id_t *es_source,
+                       bool es_source_is_master,
+                       const struct vlc_player_timer_point *point,
+                       unsigned frame_rate, unsigned frame_rate_base)
+{
+    struct vlc_player_timer_source *source;
+    assert(point);
+
+    vlc_mutex_lock(&player->timer.lock);
+
+    if (!es_source) /* input source */
+    {
+        /* Only valid for input sources */
+        if (player->timer.input_length != point->length)
+            player->timer.input_length = point->length;
+        /* Will likely be overridden by non input source */
+        player->timer.input_position = point->position;
+        if (point->ts == VLC_TICK_INVALID)
+        {
+            /* ts can only be invalid from the input source */
+            vlc_mutex_unlock(&player->timer.lock);
+            return;
+        }
+    }
+
+    source = &player->timer.best_source;
+    /* Best source priority:
+     * 1/ es_source != NULL + master (from the master ES track)
+     * 2/ es_source != NULL (from the first ES track updated)
+     * 3/ es_source == NULL (from the input)
+     */
+    if (!source->es || es_source_is_master)
+        source->es = es_source;
+
+    /* Notify the best source, but don't notify when an output was rendered
+     * while the clock was paused (system_date == INT64_MAX */
+    if (source->es == es_source && point->system_date != INT64_MAX)
+    {
+        bool force_update;
+        vlc_player_UpdateTimerSource(player, source, point->rate, point->ts,
+                                     point->system_date, &force_update);
+        if (!vlc_list_is_empty(&source->listeners))
+            vlc_player_SendTimerSourceUpdates(player, source, force_update,
+                                              VLC_PLAYER_TIMER_STATE_PLAYING,
+                                              &source->point);
+    }
+
+    source = &player->timer.smpte_source;
+    /* SMPTE source: only the video source */
+    if (!source->es && es_source && vlc_es_id_GetCat(es_source) == VIDEO_ES)
+        source->es = es_source;
+
+    /* Notify the SMPTE source, also notify when the video output was rendered
+     * while the clock was paused */
+    if (source->es == es_source && source->es)
+    {
+        assert(frame_rate != 0 && frame_rate_base != 0);
+        if (frame_rate != source->smpte.frame_rate
+         || frame_rate_base != source->smpte.frame_rate_base)
+            vlc_player_UpdateSmpteTimerFPS(player, source, frame_rate,
+                                           frame_rate_base);
+
+        bool unused;
+        vlc_player_UpdateTimerSource(player, source, point->rate, point->ts,
+                                     point->system_date, &unused);
+        if (!vlc_list_is_empty(&source->listeners))
+            vlc_player_SendSmpteTimerSourceUpdates(player, source,
+                                                   &source->point);
+    }
+
+    vlc_mutex_unlock(&player->timer.lock);
+}
+
+void
+vlc_player_RemoveTimerSource(vlc_player_t *player, vlc_es_id_t *es_source)
+{
+    vlc_mutex_lock(&player->timer.lock);
+    for (size_t i = 0; i < VLC_PLAYER_TIMER_TYPE_COUNT; ++i)
+    {
+        struct vlc_player_timer_source *source = &player->timer.sources[i];
+        if (source->es == es_source)
+        {
+            /* Discontinuity should have been already signaled */
+            assert(source->point.system_date == VLC_TICK_INVALID);
+            source->es = NULL;
+        }
+    }
+    vlc_mutex_unlock(&player->timer.lock);
+}
+
+int
+vlc_player_GetTimerPoint(vlc_player_t *player, vlc_tick_t system_now,
+                         vlc_tick_t *out_ts, float *out_pos)
+{
+    vlc_mutex_lock(&player->timer.lock);
+    if (player->timer.best_source.point.system_date == VLC_TICK_INVALID)
+    {
+        vlc_mutex_unlock(&player->timer.lock);
+        return VLC_EGENERIC;
+    }
+    int ret = vlc_player_timer_point_Interpolate(&player->timer.best_source.point,
+                                                 system_now, out_ts, out_pos);
+
+    vlc_mutex_unlock(&player->timer.lock);
+    return ret;
+}
+
+vlc_player_timer_id *
+vlc_player_AddTimer(vlc_player_t *player, vlc_tick_t min_period,
+                    const struct vlc_player_timer_cbs *cbs, void *data)
+{
+    assert(min_period >= VLC_TICK_0 || min_period == VLC_TICK_INVALID);
+    assert(cbs && cbs->on_update);
+
+    struct vlc_player_timer_id *timer = malloc(sizeof(*timer));
+    if (!timer)
+        return NULL;
+    timer->period = min_period;
+    timer->last_update_date = VLC_TICK_INVALID;
+    timer->cbs = cbs;
+    timer->data = data;
+
+    vlc_mutex_lock(&player->timer.lock);
+    vlc_list_append(&timer->node, &player->timer.best_source.listeners);
+    vlc_mutex_unlock(&player->timer.lock);
+
+    return timer;
+}
+
+vlc_player_timer_id *
+vlc_player_AddSmpteTimer(vlc_player_t *player,
+                         const struct vlc_player_timer_smpte_cbs *cbs,
+                         void *data)
+{
+    assert(cbs && cbs->on_update);
+
+    struct vlc_player_timer_id *timer = malloc(sizeof(*timer));
+    if (!timer)
+        return NULL;
+    timer->period = VLC_TICK_INVALID;
+    timer->last_update_date = VLC_TICK_INVALID;
+    timer->smpte_cbs = cbs;
+    timer->data = data;
+
+    vlc_mutex_lock(&player->timer.lock);
+    vlc_list_append(&timer->node, &player->timer.smpte_source.listeners);
+    vlc_mutex_unlock(&player->timer.lock);
+
+    return timer;
+}
+
+void
+vlc_player_RemoveTimer(vlc_player_t *player, vlc_player_timer_id *timer)
+{
+    assert(timer);
+
+    vlc_mutex_lock(&player->timer.lock);
+    vlc_list_remove(&timer->node);
+    vlc_mutex_unlock(&player->timer.lock);
+
+    free(timer);
+}
+
+int
+vlc_player_timer_point_Interpolate(const struct vlc_player_timer_point *point,
+                                   vlc_tick_t system_now,
+                                   vlc_tick_t *out_ts, float *out_pos)
+{
+    assert(point);
+    assert(system_now > 0);
+    assert(out_ts || out_pos);
+
+    const vlc_tick_t drift = (system_now - point->system_date) * point->rate;
+    vlc_tick_t ts = point->ts;
+    float pos = point->position;
+
+    if (ts != VLC_TICK_INVALID)
+    {
+        ts += drift;
+        if (ts < 0)
+            return VLC_EGENERIC;
+    }
+    if (point->length != VLC_TICK_INVALID)
+    {
+        pos += drift / (float) point->length;
+        if (pos > 1.f)
+            pos = 1.f;
+        if (ts > point->length)
+            ts = point->length;
+    }
+
+    if (out_ts)
+        *out_ts = ts;
+    if (out_pos)
+        *out_pos = pos;
+
+    return VLC_SUCCESS;
+}
+
+void
+vlc_player_InitTimer(vlc_player_t *player)
+{
+    vlc_mutex_init(&player->timer.lock);
+
+    for (size_t i = 0; i < VLC_PLAYER_TIMER_TYPE_COUNT; ++i)
+    {
+        vlc_list_init(&player->timer.sources[i].listeners);
+        player->timer.sources[i].point.system_date = VLC_TICK_INVALID;
+        player->timer.sources[i].es = NULL;
+    }
+    player->timer.smpte_source.smpte.last_ts = VLC_TICK_INVALID;
+    player->timer.input_length = VLC_TICK_INVALID;
+    player->timer.input_position = 0.f;
+}
+
+void
+vlc_player_DestroyTimer(vlc_player_t *player)
+{
+    for (size_t i = 0; i < VLC_PLAYER_TIMER_TYPE_COUNT; ++i)
+        assert(vlc_list_is_empty(&player->timer.sources[i].listeners));
+    vlc_mutex_destroy(&player->timer.lock);
+}
-- 
2.20.1




More information about the vlc-devel mailing list