You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

755 lines
25 KiB

/*****************************************************************************
* 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.input_live = false;
player->timer.input_length = VLC_TICK_INVALID;
player->timer.input_normal_time = VLC_TICK_0;
player->timer.last_ts = VLC_TICK_INVALID;
player->timer.start_offset = 0;
player->timer.input_position = 0;
player->timer.smpte_source.smpte.last_framenum = ULONG_MAX;
player->timer.seek_ts = VLC_TICK_INVALID;
player->timer.seek_position = -1;
player->timer.update_state = UPDATE_STATE_RESUMED;
player->timer.stopping = false;
vlc_mutex_unlock(&player->timer.lock);
}
static void
vlc_player_SendTimerSeek(vlc_player_t *player,
struct vlc_player_timer_source *source,
const struct vlc_player_timer_point *point,
bool is_smpte)
{
(void) player;
vlc_player_timer_id *timer;
vlc_list_foreach(timer, &source->listeners, node)
{
if (is_smpte)
{
if (timer->smpte_cbs->on_seek != NULL)
timer->smpte_cbs->on_seek(point, timer->data);
}
else
{
if (timer->cbs->on_seek != NULL)
timer->cbs->on_seek(point, timer->data);
}
}
}
static void
vlc_player_SendTimerPause(vlc_player_t *player,
struct vlc_player_timer_source *source,
vlc_tick_t system_date, bool is_smpte)
{
(void) player;
vlc_player_timer_id *timer;
vlc_list_foreach(timer, &source->listeners, node)
{
if (is_smpte)
{
if (timer->smpte_cbs->on_paused != NULL)
timer->smpte_cbs->on_paused(system_date, timer->data);
}
else
{
timer->last_update_date = VLC_TICK_INVALID;
if (timer->cbs->on_paused != NULL)
timer->cbs->on_paused(system_date, timer->data);
}
}
}
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 == VLC_TICK_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 == VLC_TICK_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_UpdateTimerEvent(vlc_player_t *player, vlc_es_id_t *es_source,
enum vlc_player_timer_event event,
vlc_tick_t system_date)
{
vlc_mutex_lock(&player->timer.lock);
/* 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. */
switch (event)
{
case VLC_PLAYER_TIMER_EVENT_DISCONTINUITY:
assert(system_date == VLC_TICK_INVALID);
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)
continue;
/* There can be several discontinuities on the same source
* for one seek request, hence the need of the
* 'timer.seeking' variable to notify only once the end of
* the seek request. */
if (source->seeking)
{
source->seeking = false;
vlc_player_SendTimerSeek(player, source, NULL,
i == VLC_PLAYER_TIMER_TYPE_SMPTE);
}
source->point.system_date = VLC_TICK_INVALID;
}
break;
case VLC_PLAYER_TIMER_EVENT_PAUSED:
assert(system_date != VLC_TICK_INVALID);
player->timer.update_state = UPDATE_STATE_PAUSED;
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)
continue;
vlc_player_SendTimerPause(player, source, system_date,
i == VLC_PLAYER_TIMER_TYPE_SMPTE);
}
break;
case VLC_PLAYER_TIMER_EVENT_PLAYING:
assert(!player->timer.stopping);
player->timer.update_state = UPDATE_STATE_RESUMED;
break;
case VLC_PLAYER_TIMER_EVENT_STOPPING:
player->timer.stopping = true;
for (size_t i = 0; i < VLC_PLAYER_TIMER_TYPE_COUNT; ++i)
{
struct vlc_player_timer_source *source = &player->timer.sources[i];
vlc_player_SendTimerPause(player, source, system_date,
i == VLC_PLAYER_TIMER_TYPE_SMPTE);
}
break;
default:
vlc_assert_unreachable();
}
vlc_mutex_unlock(&player->timer.lock);
}
void
vlc_player_UpdateTimerSeekState(vlc_player_t *player, vlc_tick_t time,
double position)
{
vlc_mutex_lock(&player->timer.lock);
struct vlc_player_timer_source *source = &player->timer.best_source;
if (time == VLC_TICK_INVALID)
{
assert(position >= 0);
if (source->point.length != VLC_TICK_INVALID)
player->timer.seek_ts = position * source->point.length;
else
player->timer.seek_ts = VLC_TICK_INVALID;
}
else
player->timer.seek_ts = time;
if (position < 0)
{
assert(time != VLC_TICK_INVALID);
if (source->point.length != VLC_TICK_INVALID && !source->point.live)
player->timer.seek_position = time / (double) source->point.length;
}
else
player->timer.seek_position = position;
const struct vlc_player_timer_point point =
{
.position = player->timer.seek_position,
.rate = source->point.rate,
.ts = player->timer.seek_ts,
.length = source->point.length,
.live = source->point.live,
.system_date = VLC_TICK_MAX,
};
source->seeking = true;
vlc_player_SendTimerSeek(player, source, &point, false);
source = &player->timer.smpte_source;
source->seeking = true;
vlc_player_SendTimerSeek(player, source, &point, true);
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 - player->timer.start_offset + VLC_TICK_0;
source->point.length = player->timer.input_length;
source->point.live = player->timer.input_live;
/* 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 = VLC_TICK_MAX;
else
source->point.system_date = system_date;
if (source->point.length != VLC_TICK_INVALID && !source->point.live)
source->point.position = (ts - player->timer.input_normal_time - player->timer.start_offset)
/ (double) source->point.length;
else
source->point.position = player->timer.input_position;
}
static void
vlc_player_UpdateTimerBestSource(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 system_date,
bool force_update)
{
/* 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)
*/
struct vlc_player_timer_source *source = &player->timer.best_source;
if (!source->es || es_source_is_master)
source->es = es_source;
/* Notify the best source */
if (source->es == es_source || force_update)
{
if (source->point.rate != point->rate)
{
player->timer.last_ts = VLC_TICK_INVALID;
force_update = true;
}
/* When paused (VLC_TICK_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
|| (source->point.system_date != system_date && system_date != VLC_TICK_MAX))
{
vlc_player_UpdateTimerSource(player, source, point->rate, point->ts,
system_date);
player->timer.last_ts = point->ts;
/* It is possible to receive valid points while seeking. These
* points could be updated when the input thread didn't yet process
* the seek request. */
if (!source->seeking)
{
/* Reset seek time/position now that we receive a valid point
* and seek was processed */
player->timer.seek_ts = VLC_TICK_INVALID;
player->timer.seek_position = -1;
}
if (!vlc_list_is_empty(&source->listeners))
vlc_player_SendTimerSourceUpdates(player, source, force_update,
&source->point);
}
}
}
static void
vlc_player_UpdateTimerSmpteSource(vlc_player_t *player, vlc_es_id_t *es_source,
const struct vlc_player_timer_point *point,
vlc_tick_t system_date,
unsigned frame_rate, unsigned frame_rate_base)
{
struct vlc_player_timer_source *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)
{
if (frame_rate != 0 && (frame_rate != source->smpte.frame_rate
|| frame_rate_base != source->smpte.frame_rate_base))
{
assert(frame_rate_base != 0);
vlc_player_UpdateSmpteTimerFPS(player, source, frame_rate,
frame_rate_base);
}
if (source->smpte.frame_rate != 0)
{
vlc_player_UpdateTimerSource(player, source, point->rate, point->ts,
system_date);
if (!vlc_list_is_empty(&source->listeners))
vlc_player_SendSmpteTimerSourceUpdates(player, source,
&source->point);
}
}
}
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,
vlc_tick_t start_offset)
{
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 (normal_time != VLC_TICK_INVALID
&& 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
&& point->length >= VLC_TICK_0)
{
player->timer.input_length = point->length;
player->timer.last_ts = VLC_TICK_INVALID;
force_update = true;
}
if (player->timer.input_live != point->live)
{
player->timer.input_live = point->live;
force_update = true;
}
if (start_offset > 0)
player->timer.start_offset = start_offset;
/* 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);
vlc_tick_t system_date = point->system_date;
if (player->timer.update_state == UPDATE_STATE_PAUSED)
{
if (es_source != NULL && point->system_date == VLC_TICK_MAX)
{
/* Update was forced, probably a next/prev frame */
force_update = true;
}
system_date = VLC_TICK_MAX;
}
if (!player->timer.stopping)
vlc_player_UpdateTimerBestSource(player, es_source,
es_source_is_master, point, system_date,
force_update);
vlc_player_UpdateTimerSmpteSource(player, es_source, point, system_date,
frame_rate, frame_rate_base);
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);
struct vlc_player_timer_source *bestsource = &player->timer.best_source;
struct vlc_player_timer_source *smptesource = &player->timer.smpte_source;
/* Unlikely case where the source ES is deleted while seeking */
if (bestsource->es == es_source && bestsource->seeking)
{
bestsource->seeking = false;
vlc_player_SendTimerSeek(player, bestsource, NULL, false);
}
if (smptesource->es == es_source && smptesource->seeking)
{
smptesource->seeking = false;
vlc_player_SendTimerSeek(player, smptesource, NULL, true);
}
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, bool *seeking,
vlc_tick_t system_now,
vlc_tick_t *out_ts, double *out_pos)
{
int ret = VLC_EGENERIC;
vlc_mutex_lock(&player->timer.lock);
bool timer_seeking = player->timer.seek_ts != VLC_TICK_INVALID
|| player->timer.seek_position >= 0.0f;
if (*seeking && timer_seeking)
{
if (out_ts != NULL)
{
if (player->timer.seek_ts == VLC_TICK_INVALID)
goto end;
*out_ts = player->timer.seek_ts;
}
if (out_pos != NULL)
{
if (player->timer.seek_position < 0)
goto end;
*out_pos = player->timer.seek_position;
}
ret = VLC_SUCCESS;
goto end;
}
if (player->timer.best_source.point.system_date == VLC_TICK_INVALID)
goto end;
if (system_now != VLC_TICK_INVALID
&& player->timer.update_state == UPDATE_STATE_RESUMED)
ret = vlc_player_timer_point_Interpolate(&player->timer.best_source.point,
system_now, out_ts, out_pos);
else
{
const struct vlc_player_timer_point *point =
&player->timer.best_source.point;
if (out_ts)
*out_ts = point->ts;
if (out_pos)
*out_pos = point->position;
ret = VLC_SUCCESS;
}
if (ret == VLC_SUCCESS)
*seeking = timer_seeking;
end:
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, double *out_pos)
{
assert(point);
assert(system_now > 0);
assert(out_ts || out_pos);
/* A system_date == VLC_TICK_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 == VLC_TICK_MAX ? 0
: (system_now - point->system_date) * point->rate;
vlc_tick_t ts = point->ts;
double 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 && !point->live)
{
pos += drift / (double) 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;
player->timer.sources[i].seeking = false;
}
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));
}