[vlc-commits] player: add the timer API

Thomas Guillem git at videolan.org
Mon Sep 23 13:36:24 CEST 2019


vlc | branch: master | Thomas Guillem <thomas at gllm.fr> | Tue Aug 13 15:37:13 2019 +0200| [9733154628772eddcd8d4a544ca75f613c02512a] | committer: Thomas Guillem

player: add the timer API

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.

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=9733154628772eddcd8d4a544ca75f613c02512a
---

 include/vlc_player.h | 200 +++++++++++++++++++
 src/Makefile.am      |   1 +
 src/libvlccore.sym   |   5 +
 src/player/input.c   |  87 ++++++++-
 src/player/player.c  |   7 +
 src/player/player.h  | 105 ++++++++++
 src/player/timer.c   | 540 +++++++++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 944 insertions(+), 1 deletion(-)

diff --git a/include/vlc_player.h b/include/vlc_player.h
index bf79cdba6e..db19966080 100644
--- a/include/vlc_player.h
+++ b/include/vlc_player.h
@@ -3047,6 +3047,206 @@ 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 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 or in the past. The special value of INT64_MAX mean that the
+     * clock was paused when this point was updated. In that case,
+     * vlc_player_timer_point_Interpolate() will return the current ts/pos of
+     * this point (there is nothing to interpolate). */
+    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 value always valid, the time corresponding to the state
+     * @param data opaque pointer set by vlc_player_AddTimer()
+     */
+    void (*on_update)(const struct vlc_player_timer_point *value, void *data);
+
+    /**
+     * The player is paused or a discontinuity occurred, likely caused by seek
+     * from the user or because the playback is stopped. The player user should
+     * stop its "interpolate" timer.
+     *
+     * @param system_date system date of this event, only valid when paused. It
+     * can be used to interpolate the last updated point to this date in order
+     * to get the last paused ts/position.
+     * @param data opaque pointer set by vlc_player_AddTimer()
+     */
+    void (*on_discontinuity)(vlc_tick_t system_date, 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, subtract this time
+ * with VLC_TICK_0 to get the original value.
+ * @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);
+
+/**
+ * Get the date of the next interval
+ *
+ * Can be used to setup an UI timer in order to update some widgets at specific
+ * interval. A next_interval of VLC_TICK_FROM_SEC(1) can be used to update a
+ * time widget when the media reaches a new second.
+ *
+ * @note The media time doesn't necessarily correspond to the system time, that
+ * is why this function is needed and use the rate of the current point.
+ *
+ * @param point time update obtained via the vlc_player_timer_cbs.on_update()
+ * @param system_now current system date
+ * @param interpolated_ts ts returned by vlc_player_timer_point_Interpolate()
+ * with the same system now
+ * @param next_interval next interval
+ * @return the absolute system date of the next interval
+ */
+VLC_API vlc_tick_t
+vlc_player_timer_point_GetNextIntervalDate(const struct vlc_player_timer_point *point,
+                                           vlc_tick_t system_now,
+                                           vlc_tick_t interpolated_ts,
+                                           vlc_tick_t next_interval);
+
+/** @} 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 e5241c411d..960541512b 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -767,6 +767,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
@@ -825,6 +827,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
@@ -861,6 +864,8 @@ vlc_player_SetTeletextTransparency
 vlc_player_SetTrackCategoryEnabled
 vlc_player_Start
 vlc_player_Stop
+vlc_player_timer_point_GetNextIntervalDate
+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 05e817f1a7..06f75fac9b 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;
 }
 
@@ -140,6 +152,8 @@ vlc_player_input_HandleState(struct vlc_player_input *input,
                 vlc_player_SendEvent(player, on_titles_changed, NULL);
             }
 
+            vlc_player_ResetTimer(player);
+
             if (input->error != VLC_PLAYER_ERROR_NONE)
                 player->error_count++;
             else
@@ -172,6 +186,11 @@ vlc_player_input_HandleState(struct vlc_player_input *input,
             break;
         case VLC_PLAYER_STATE_STOPPING:
             input->started = false;
+
+            vlc_player_UpdateTimerState(player, NULL,
+                                        VLC_PLAYER_TIMER_STATE_DISCONTINUITY,
+                                        VLC_TICK_INVALID);
+
             if (input == player->input)
                 player->input = NULL;
 
@@ -183,8 +202,10 @@ vlc_player_input_HandleState(struct vlc_player_input *input,
             }
             send_event = !player->started;
             break;
-        case VLC_PLAYER_STATE_STARTED:
         case VLC_PLAYER_STATE_PLAYING:
+            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 +213,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);
             break;
         default:
             vlc_assert_unreachable();
@@ -446,6 +473,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 +601,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,
+                                   VLC_TICK_INVALID,
+                                   event->output_clock.frame_rate,
+                                   event->output_clock.frame_rate_base);
+        }
+        else
+        {
+            vlc_player_UpdateTimerState(player, event->output_clock.id,
+                                        VLC_PLAYER_TIMER_STATE_DISCONTINUITY,
+                                        VLC_TICK_INVALID);
+        }
+        return;
+    }
+
     vlc_mutex_lock(&player->lock);
 
     switch (event->type)
@@ -594,12 +649,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 +670,30 @@ 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 (input->normal_time != event->times.normal_time)
+            {
+                assert(event->times.normal_time != VLC_TICK_INVALID);
+                input->normal_time = event->times.normal_time;
+                changed = true;
+            }
+
+            if (changed)
+            {
+                const struct vlc_player_timer_point point = {
+                    .position = input->position,
+                    .rate = input->rate,
+                    .ts = input->time + input->normal_time,
+                    .length = input->length,
+                    .system_date = system_date,
+                };
+                vlc_player_UpdateTimer(player, NULL, false, &point,
+                                       input->normal_time, 0, 0);
             }
             break;
+        }
         case INPUT_EVENT_PROGRAM:
             vlc_player_input_HandleProgramEvent(input, &event->program);
             break;
@@ -694,6 +777,8 @@ 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->normal_time = VLC_TICK_0;
+    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 d487fdeadc..76e9f1d5d8 100644
--- a/src/player/player.c
+++ b/src/player/player.c
@@ -910,6 +910,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);
 }
@@ -1867,6 +1870,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);
 
@@ -1968,11 +1973,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..b83ba55b4c 100644
--- a/src/player/player.h
+++ b/src/player/player.h
@@ -64,6 +64,9 @@ struct vlc_player_input
 
     float position;
     vlc_tick_t time;
+    vlc_tick_t normal_time;
+
+    vlc_tick_t pause_date;
 
     bool recording;
 
@@ -121,6 +124,71 @@ 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 {
+            unsigned long last_framenum;
+            unsigned frame_rate;
+            unsigned frame_rate_base;
+            unsigned frame_resolution;
+            unsigned df_fps;
+            int df;
+            int frames_per_10mins;
+        } smpte;
+    };
+};
+
+enum vlc_player_timer_state
+{
+    VLC_PLAYER_TIMER_STATE_PLAYING,
+    VLC_PLAYER_TIMER_STATE_PAUSED,
+    VLC_PLAYER_TIMER_STATE_DISCONTINUITY,
+};
+
+struct vlc_player_timer
+{
+    vlc_mutex_t lock;
+
+    enum vlc_player_timer_state state;
+    bool seeking;
+
+    vlc_tick_t input_length;
+    vlc_tick_t input_normal_time;
+    vlc_tick_t last_ts;
+    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 +235,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 +386,41 @@ 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_ResetTimer(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 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,
+                       vlc_tick_t normal_time,
+                       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..53e027cedb
--- /dev/null
+++ b/src/player/timer.c
@@ -0,0 +1,540 @@
+/*****************************************************************************
+ * 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"
+
+void
+vlc_player_ResetTimer(vlc_player_t *player)
+{
+    vlc_mutex_lock(&player->timer.lock);
+
+    player->timer.state = VLC_PLAYER_TIMER_STATE_DISCONTINUITY;
+    player->timer.input_length = VLC_TICK_INVALID;
+    player->timer.input_normal_time = VLC_TICK_0;
+    player->timer.last_ts = VLC_TICK_INVALID;
+    player->timer.input_position = 0.f;
+    player->timer.smpte_source.smpte.last_framenum = ULONG_MAX;
+
+    vlc_mutex_unlock(&player->timer.lock);
+}
+
+static void
+vlc_player_SendTimerSourceUpdates(vlc_player_t *player,
+                                  struct vlc_player_timer_source *source,
+                                  bool force_update,
+                                  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 == INT64_MAX /* always update when paused */
+         || point->system_date - timer->last_update_date >= timer->period)
+        {
+            timer->cbs->on_update(point, timer->data);
+            timer->last_update_date = point->system_date == INT64_MAX ?
+                                      VLC_TICK_INVALID : 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;
+
+    struct vlc_player_timer_smpte_timecode tc;
+    unsigned 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;
+
+        /* Convert the ts to a frame number */
+        framenum = round(point->ts * 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;
+
+        /* Use 30 or 60 framerates for the next frames/seconds/minutes/hours
+         * calculaton */
+        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;
+
+        /* Convert the ts to a frame number */
+        framenum = round(point->ts * frame_rate
+                         / (double) frame_rate_base / VLC_TICK_FROM_SEC(1));
+
+        tc.drop_frame = false;
+    }
+    if (framenum == source->smpte.last_framenum)
+        return;
+
+    source->smpte.last_framenum = framenum;
+
+    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.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 system_date)
+{
+    vlc_mutex_lock(&player->timer.lock);
+
+    if (state == VLC_PLAYER_TIMER_STATE_DISCONTINUITY)
+    {
+        assert(system_date == VLC_TICK_INVALID);
+
+        /* 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;
+        }
+    }
+    else
+    {
+        assert(state == VLC_PLAYER_TIMER_STATE_PAUSED);
+        assert(system_date != VLC_TICK_INVALID);
+    }
+
+    player->timer.state = state;
+
+    struct vlc_player_timer_source *source = &player->timer.best_source;
+    vlc_player_timer_id *timer;
+    vlc_list_foreach(timer, &source->listeners, node)
+    {
+        timer->last_update_date = VLC_TICK_INVALID;
+        timer->cbs->on_discontinuity(system_date, timer->data);
+    }
+
+    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)
+{
+    assert(ts >= VLC_TICK_0);
+    assert(player->timer.input_normal_time >= VLC_TICK_0);
+
+    source->point.rate = rate;
+    source->point.ts = ts - player->timer.input_normal_time + VLC_TICK_0;
+    source->point.length = player->timer.input_length;
+
+    /* Put an invalid date for the first point in order to disable
+     * interpolation (behave as paused), indeed, we should wait for one more
+     * point before starting interpolation (ideally, it should be more) */
+    if (source->point.system_date == VLC_TICK_INVALID)
+        source->point.system_date = INT64_MAX;
+    else
+        source->point.system_date = system_date;
+
+    if (source->point.length != VLC_TICK_INVALID)
+        source->point.position = (ts - player->timer.input_normal_time)
+                               / (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,
+                       vlc_tick_t normal_time,
+                       unsigned frame_rate, unsigned frame_rate_base)
+{
+    struct vlc_player_timer_source *source;
+    assert(point);
+    /* A null source can't be the master */
+    assert(es_source == NULL ? !es_source_is_master : true);
+
+    vlc_mutex_lock(&player->timer.lock);
+
+    bool force_update = false;
+    if (!es_source) /* input source */
+    {
+        /* Only valid for input sources */
+        if (player->timer.input_normal_time != normal_time)
+        {
+            player->timer.input_normal_time = normal_time;
+            player->timer.last_ts = VLC_TICK_INVALID;
+            force_update = true;
+        }
+        if (player->timer.input_length != point->length)
+        {
+            player->timer.input_length = point->length;
+            player->timer.last_ts = VLC_TICK_INVALID;
+            force_update = true;
+        }
+        /* Will likely be overridden by non input source */
+        player->timer.input_position = point->position;
+
+        if (point->ts == VLC_TICK_INVALID
+         || point->system_date == VLC_TICK_INVALID)
+        {
+            /* ts can only be invalid from the input source */
+            vlc_mutex_unlock(&player->timer.lock);
+            return;
+        }
+    }
+
+    assert(point->ts != VLC_TICK_INVALID);
+
+    /* An update after a discontinuity means that the playback is resumed */
+    if (player->timer.state == VLC_PLAYER_TIMER_STATE_DISCONTINUITY)
+        player->timer.state = VLC_PLAYER_TIMER_STATE_PLAYING;
+
+    /* Best source priority:
+     * 1/ es_source != NULL when paused (any ES tracks when paused. Indeed,
+     * there is likely no audio update (master) when paused but only video
+     * ones, via vlc_player_NextVideoFrame() for example)
+     * 2/ es_source != NULL + master (from the master ES track)
+     * 3/ es_source != NULL (from the first ES track updated)
+     * 4/ es_source == NULL (from the input)
+     */
+    source = &player->timer.best_source;
+    if (!source->es || es_source_is_master
+     || (es_source && player->timer.state == VLC_PLAYER_TIMER_STATE_PAUSED))
+        source->es = es_source;
+
+    /* Notify the best source */
+    if (source->es == es_source)
+    {
+        if (source->point.rate != point->rate)
+        {
+            player->timer.last_ts = VLC_TICK_INVALID;
+            force_update = true;
+        }
+
+        /* When paused (INT64_MAX), the same ts can be send more than one time
+         * from the video source, only send it if different in that case. */
+        if (point->ts != player->timer.last_ts
+          || point->system_date != INT64_MAX)
+        {
+            vlc_player_UpdateTimerSource(player, source, point->rate, point->ts,
+                                         point->system_date);
+
+            if (!vlc_list_is_empty(&source->listeners))
+                vlc_player_SendTimerSourceUpdates(player, source, force_update,
+                                                  &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)
+        {
+            player->timer.last_ts = VLC_TICK_INVALID;
+            vlc_player_UpdateSmpteTimerFPS(player, source, frame_rate,
+                                           frame_rate_base);
+        }
+
+        if (point->ts != player->timer.last_ts)
+        {
+            vlc_player_UpdateTimerSource(player, source, point->rate, point->ts,
+                                         point->system_date);
+
+            if (!vlc_list_is_empty(&source->listeners))
+                vlc_player_SendSmpteTimerSourceUpdates(player, source,
+                                                       &source->point);
+        }
+    }
+
+    player->timer.last_ts = point->ts;
+
+    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);
+
+    /* A system_date == INT64_MAX means the clock was paused when it updated
+     * this point, so there is nothing to interpolate */
+    const vlc_tick_t drift = point->system_date == INT64_MAX ? 0
+                           : (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 (unlikely(ts < VLC_TICK_0))
+            return VLC_EGENERIC;
+    }
+    if (point->length != VLC_TICK_INVALID)
+    {
+        pos += drift / (float) point->length;
+        if (unlikely(pos < 0.f))
+            return VLC_EGENERIC;
+        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;
+}
+
+vlc_tick_t
+vlc_player_timer_point_GetNextIntervalDate(const struct vlc_player_timer_point *point,
+                                           vlc_tick_t system_now,
+                                           vlc_tick_t interpolated_ts,
+                                           vlc_tick_t next_interval)
+{
+    assert(point);
+    assert(system_now > 0);
+    assert(next_interval != VLC_TICK_INVALID);
+
+    const unsigned ts_interval = interpolated_ts / next_interval;
+    const vlc_tick_t ts_next_interval =
+        ts_interval * next_interval + next_interval;
+
+    return (ts_next_interval - interpolated_ts) / point->rate + system_now;
+}
+
+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;
+    }
+    vlc_player_ResetTimer(player);
+}
+
+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);
+}




More information about the vlc-commits mailing list