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.
306 lines
8.5 KiB
306 lines
8.5 KiB
/*****************************************************************************
|
|
* pcr_sync.c
|
|
*****************************************************************************
|
|
* Copyright (C) 2022 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 <assert.h>
|
|
#include <stdint.h>
|
|
|
|
#include <vlc_common.h>
|
|
|
|
#include <vlc_frame.h>
|
|
#include <vlc_list.h>
|
|
#include <vlc_tick.h>
|
|
#include <vlc_vector.h>
|
|
|
|
#include "pcr_sync.h"
|
|
|
|
struct es_dts_entry
|
|
{
|
|
vlc_tick_t dts;
|
|
bool passed;
|
|
vlc_tick_t discontinuity;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
vlc_tick_t pcr;
|
|
struct VLC_VECTOR(struct es_dts_entry) es_last_dts_entries;
|
|
size_t entries_left;
|
|
bool no_frame_before;
|
|
|
|
struct vlc_list node;
|
|
} pcr_event_t;
|
|
|
|
static inline void pcr_event_Delete(pcr_event_t *ev)
|
|
{
|
|
vlc_vector_destroy(&ev->es_last_dts_entries);
|
|
free(ev);
|
|
}
|
|
|
|
struct es_data
|
|
{
|
|
bool is_deleted;
|
|
vlc_tick_t last_input_dts;
|
|
vlc_tick_t last_output_dts;
|
|
vlc_tick_t discontinuity;
|
|
pcr_event_t *last_pcr_event;
|
|
};
|
|
|
|
static inline struct es_data es_data_Init(void)
|
|
{
|
|
return (struct es_data){.is_deleted = false,
|
|
.last_input_dts = VLC_TICK_INVALID,
|
|
.last_output_dts = VLC_TICK_INVALID,
|
|
.discontinuity = VLC_TICK_INVALID};
|
|
}
|
|
|
|
struct es_data_vec VLC_VECTOR(struct es_data);
|
|
|
|
struct vlc_pcr_sync
|
|
{
|
|
struct vlc_list pcr_events;
|
|
struct es_data_vec es_data;
|
|
vlc_mutex_t lock;
|
|
};
|
|
|
|
vlc_pcr_sync_t *vlc_pcr_sync_New(void)
|
|
{
|
|
vlc_pcr_sync_t *ret = malloc(sizeof(*ret));
|
|
if (unlikely(ret == NULL))
|
|
return NULL;
|
|
|
|
vlc_vector_init(&ret->es_data);
|
|
vlc_list_init(&ret->pcr_events);
|
|
vlc_mutex_init(&ret->lock);
|
|
return ret;
|
|
}
|
|
|
|
void vlc_pcr_sync_Delete(vlc_pcr_sync_t *pcr_sync)
|
|
{
|
|
vlc_vector_destroy(&pcr_sync->es_data);
|
|
|
|
pcr_event_t *it;
|
|
vlc_list_foreach(it, &pcr_sync->pcr_events, node) { pcr_event_Delete(it); }
|
|
|
|
free(pcr_sync);
|
|
}
|
|
|
|
static bool pcr_sync_ShouldFastForwardPCR(vlc_pcr_sync_t *pcr_sync)
|
|
{
|
|
struct es_data it;
|
|
vlc_vector_foreach(it, &pcr_sync->es_data)
|
|
{
|
|
if (!it.is_deleted && it.last_output_dts != it.last_input_dts)
|
|
return false;
|
|
}
|
|
return vlc_list_is_empty(&pcr_sync->pcr_events);
|
|
}
|
|
|
|
static bool pcr_sync_HadFrameInputSinceLastPCR(vlc_pcr_sync_t *pcr_sync)
|
|
{
|
|
const pcr_event_t *pcr_event =
|
|
vlc_list_last_entry_or_null(&pcr_sync->pcr_events, pcr_event_t, node);
|
|
|
|
if (pcr_event == NULL)
|
|
return true;
|
|
|
|
for (unsigned int i = 0; i < pcr_sync->es_data.size; ++i)
|
|
{
|
|
const struct es_data *curr = &pcr_sync->es_data.data[i];
|
|
if (curr->is_deleted || curr->last_input_dts == VLC_TICK_INVALID)
|
|
continue;
|
|
|
|
const bool is_oob = i >= pcr_event->es_last_dts_entries.size;
|
|
if (!is_oob && curr->last_input_dts != pcr_event->es_last_dts_entries.data[i].dts)
|
|
return true;
|
|
else if (is_oob)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int pcr_sync_SignalPCRLocked(vlc_pcr_sync_t *pcr_sync, vlc_tick_t pcr)
|
|
{
|
|
if (pcr_sync_ShouldFastForwardPCR(pcr_sync))
|
|
return VLC_PCR_SYNC_FORWARD_PCR;
|
|
|
|
pcr_event_t *event = malloc(sizeof(*event));
|
|
if (unlikely(event == NULL))
|
|
return VLC_ENOMEM;
|
|
|
|
vlc_vector_init(&event->es_last_dts_entries);
|
|
const bool alloc_succeeded =
|
|
vlc_vector_reserve(&event->es_last_dts_entries, pcr_sync->es_data.size);
|
|
if (unlikely(alloc_succeeded == false))
|
|
{
|
|
free(event);
|
|
return VLC_ENOMEM;
|
|
}
|
|
|
|
size_t entries_left = 0;
|
|
struct es_data data;
|
|
vlc_vector_foreach(data, &pcr_sync->es_data)
|
|
{
|
|
if (!data.is_deleted)
|
|
{
|
|
vlc_vector_push(&event->es_last_dts_entries,
|
|
((struct es_dts_entry){.dts = data.last_input_dts,
|
|
.discontinuity = data.discontinuity}));
|
|
if (data.last_input_dts != VLC_TICK_INVALID)
|
|
++entries_left;
|
|
data.discontinuity = VLC_TICK_INVALID;
|
|
}
|
|
}
|
|
|
|
event->pcr = pcr;
|
|
event->entries_left = entries_left;
|
|
event->no_frame_before = !pcr_sync_HadFrameInputSinceLastPCR(pcr_sync);
|
|
|
|
vlc_list_append(&event->node, &pcr_sync->pcr_events);
|
|
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
int vlc_pcr_sync_SignalPCR(vlc_pcr_sync_t *pcr_sync, vlc_tick_t pcr)
|
|
{
|
|
vlc_mutex_lock(&pcr_sync->lock);
|
|
const int result = pcr_sync_SignalPCRLocked(pcr_sync, pcr);
|
|
vlc_mutex_unlock(&pcr_sync->lock);
|
|
return result;
|
|
}
|
|
|
|
void vlc_pcr_sync_SignalFrame(vlc_pcr_sync_t *pcr_sync, unsigned int id, const vlc_frame_t *frame)
|
|
{
|
|
vlc_mutex_lock(&pcr_sync->lock);
|
|
assert(id < pcr_sync->es_data.size);
|
|
|
|
struct es_data *data = &pcr_sync->es_data.data[id];
|
|
assert(!data->is_deleted);
|
|
if (frame->i_dts != VLC_TICK_INVALID)
|
|
data->last_input_dts = frame->i_dts;
|
|
if (frame->i_flags & VLC_FRAME_FLAG_DISCONTINUITY)
|
|
{
|
|
assert(frame->i_dts != VLC_TICK_INVALID);
|
|
data->discontinuity = frame->i_dts;
|
|
}
|
|
|
|
vlc_mutex_unlock(&pcr_sync->lock);
|
|
}
|
|
|
|
int vlc_pcr_sync_NewESID(vlc_pcr_sync_t *pcr_sync, unsigned int *id)
|
|
{
|
|
vlc_mutex_lock(&pcr_sync->lock);
|
|
|
|
for (unsigned int i = 0; i < pcr_sync->es_data.size; ++i)
|
|
{
|
|
if (pcr_sync->es_data.data[i].is_deleted)
|
|
{
|
|
pcr_sync->es_data.data[i] = es_data_Init();
|
|
vlc_mutex_unlock(&pcr_sync->lock);
|
|
*id = i;
|
|
return VLC_SUCCESS;
|
|
}
|
|
}
|
|
|
|
const bool push_succeeded = vlc_vector_push(&pcr_sync->es_data, es_data_Init());
|
|
const size_t ids_count = pcr_sync->es_data.size;
|
|
|
|
vlc_mutex_unlock(&pcr_sync->lock);
|
|
|
|
if (push_succeeded)
|
|
{
|
|
*id = ids_count - 1;
|
|
return VLC_SUCCESS;
|
|
}
|
|
return VLC_ENOMEM;
|
|
}
|
|
|
|
void vlc_pcr_sync_DelESID(vlc_pcr_sync_t *pcr_sync, unsigned int id)
|
|
{
|
|
vlc_mutex_lock(&pcr_sync->lock);
|
|
|
|
assert(id < pcr_sync->es_data.size);
|
|
pcr_sync->es_data.data[id].is_deleted = true;
|
|
|
|
vlc_mutex_unlock(&pcr_sync->lock);
|
|
}
|
|
|
|
#define pcr_event_FirstEntry(head) \
|
|
vlc_list_first_entry_or_null(head, pcr_event_t, node)
|
|
#define pcr_event_NextEntry(head, entry) \
|
|
vlc_list_next_entry_or_null(head, entry, pcr_event_t, node)
|
|
|
|
vlc_tick_t
|
|
vlc_pcr_sync_SignalFrameOutput(vlc_pcr_sync_t *pcr_sync, unsigned int id, const vlc_frame_t *frame)
|
|
{
|
|
vlc_mutex_lock(&pcr_sync->lock);
|
|
|
|
struct es_data *es = &pcr_sync->es_data.data[id];
|
|
assert(!es->is_deleted);
|
|
es->last_output_dts = frame->i_dts;
|
|
|
|
pcr_event_t *pcr_event = (es->last_pcr_event == NULL)
|
|
? pcr_event_FirstEntry(&pcr_sync->pcr_events)
|
|
: es->last_pcr_event;
|
|
if (pcr_event == NULL)
|
|
goto no_pcr;
|
|
|
|
assert(id < pcr_event->es_last_dts_entries.size);
|
|
|
|
const vlc_tick_t pcr = pcr_event->pcr;
|
|
|
|
if (pcr_event->no_frame_before)
|
|
{
|
|
es->last_pcr_event = pcr_event_NextEntry(&pcr_sync->pcr_events, pcr_event);
|
|
goto return_pcr;
|
|
}
|
|
|
|
assert(pcr_event->entries_left != 0);
|
|
|
|
struct es_dts_entry *dts_entry = &pcr_event->es_last_dts_entries.data[id];
|
|
|
|
if (dts_entry->discontinuity != VLC_TICK_INVALID && frame->i_dts > dts_entry->discontinuity)
|
|
goto no_pcr;
|
|
|
|
if (frame->i_dts < dts_entry->dts)
|
|
goto no_pcr;
|
|
|
|
if (dts_entry->passed)
|
|
goto no_pcr;
|
|
|
|
dts_entry->passed = true;
|
|
es->last_pcr_event = pcr_event_NextEntry(&pcr_sync->pcr_events, pcr_event);
|
|
if (--pcr_event->entries_left != 0)
|
|
goto no_pcr;
|
|
|
|
return_pcr:
|
|
vlc_list_remove(&pcr_event->node);
|
|
pcr_event_Delete(pcr_event);
|
|
|
|
vlc_mutex_unlock(&pcr_sync->lock);
|
|
return pcr;
|
|
|
|
no_pcr:
|
|
vlc_mutex_unlock(&pcr_sync->lock);
|
|
return VLC_TICK_INVALID;
|
|
}
|
|
|