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

/*****************************************************************************
* 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;
}