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.
 
 
 
 
 
 

192 lines
6.1 KiB

/*****************************************************************************
* pcr_helper.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 "pcr_helper.h"
#include <vlc_list.h>
#include <vlc_tick.h>
struct transcode_track_pcr_helper
{
vlc_tick_t max_delay;
/// Represents the media time held by the frame processing unit.
vlc_tick_t held_media_time;
vlc_tick_t input_media_time;
vlc_tick_t last_dts_output;
struct vlc_list delayed_frames_data;
vlc_pcr_sync_t *sync_ref;
unsigned int pcr_sync_es_id;
};
typedef struct
{
vlc_tick_t length;
vlc_tick_t dts;
vlc_tick_t media_time;
struct vlc_list node;
} delayed_frame_data_t;
transcode_track_pcr_helper_t *transcode_track_pcr_helper_New(vlc_pcr_sync_t *sync_ref,
vlc_tick_t max_delay)
{
transcode_track_pcr_helper_t *ret = malloc(sizeof(*ret));
if (unlikely(ret == NULL))
return NULL;
if (vlc_pcr_sync_NewESID(sync_ref, &ret->pcr_sync_es_id) != VLC_SUCCESS)
{
free(ret);
return NULL;
}
ret->max_delay = max_delay;
ret->held_media_time = 0;
ret->input_media_time = 0;
ret->last_dts_output = VLC_TICK_INVALID;
vlc_list_init(&ret->delayed_frames_data);
ret->sync_ref = sync_ref;
return ret;
}
void transcode_track_pcr_helper_Delete(transcode_track_pcr_helper_t *pcr_helper)
{
delayed_frame_data_t *it;
vlc_list_foreach(it, &pcr_helper->delayed_frames_data, node)
{
vlc_list_remove(&it->node);
free(it);
}
vlc_pcr_sync_DelESID(pcr_helper->sync_ref, pcr_helper->pcr_sync_es_id);
free(pcr_helper);
}
static inline vlc_tick_t
transcode_track_pcr_helper_GetFramePCR(transcode_track_pcr_helper_t *pcr_helper,
vlc_tick_t frame_dts)
{
vlc_tick_t pcr = VLC_TICK_INVALID;
vlc_tick_t it = VLC_TICK_INVALID;
// XXX: `vlc_pcr_sync_SignalFrameOutput` only needs DTS for now. Passing a frame by only filling
// the DTS is enough.
const vlc_frame_t fake_frame = {.i_dts = frame_dts};
while ((it = vlc_pcr_sync_SignalFrameOutput(pcr_helper->sync_ref, pcr_helper->pcr_sync_es_id,
&fake_frame)) != VLC_TICK_INVALID)
{
pcr = it;
}
return pcr;
}
int transcode_track_pcr_helper_SignalEnteringFrame(transcode_track_pcr_helper_t *pcr_helper,
const vlc_frame_t *frame,
vlc_tick_t *dropped_frame_ts)
{
delayed_frame_data_t *bdata = malloc(sizeof(*bdata));
if (unlikely(bdata == NULL))
return VLC_ENOMEM;
pcr_helper->input_media_time += frame->i_length;
pcr_helper->held_media_time += frame->i_length;
bdata->length = frame->i_length;
bdata->dts = frame->i_dts;
bdata->media_time = pcr_helper->input_media_time;
vlc_pcr_sync_SignalFrame(pcr_helper->sync_ref, pcr_helper->pcr_sync_es_id, frame);
vlc_list_append(&bdata->node, &pcr_helper->delayed_frames_data);
// Something went wrong in the delaying unit.
// Exceeding this limit usually means the frame was dropped. So in our case, act like it went
// through.
// TODO needs to be properly unit-tested.
if (pcr_helper->held_media_time > pcr_helper->max_delay)
{
delayed_frame_data_t *first_bdata = vlc_list_first_entry_or_null(
&pcr_helper->delayed_frames_data, delayed_frame_data_t, node);
assert(first_bdata != NULL);
const vlc_tick_t pcr = transcode_track_pcr_helper_GetFramePCR(pcr_helper, first_bdata->dts);
*dropped_frame_ts = pcr_helper->last_dts_output == VLC_TICK_INVALID
? pcr
: __MIN(pcr_helper->last_dts_output, first_bdata->dts);
pcr_helper->held_media_time -= first_bdata->length;
vlc_list_remove(&first_bdata->node);
free(first_bdata);
}
else
{
*dropped_frame_ts = VLC_TICK_INVALID;
}
return VLC_SUCCESS;
}
int transcode_track_pcr_helper_SignalLeavingFrame(transcode_track_pcr_helper_t *pcr_helper,
const vlc_frame_t *frame,
vlc_tick_t *pcr)
{
*pcr = VLC_TICK_INVALID;
if (vlc_list_is_empty(&pcr_helper->delayed_frames_data))
return VLC_EGENERIC;
pcr_helper->last_dts_output = frame->i_dts;
pcr_helper->held_media_time -= frame->i_length;
const vlc_tick_t output_media_time =
pcr_helper->input_media_time - pcr_helper->held_media_time;
delayed_frame_data_t *frame_data;
vlc_list_foreach(frame_data, &pcr_helper->delayed_frames_data, node)
{
if (output_media_time < frame_data->media_time)
break;
const vlc_tick_t current_pcr =
transcode_track_pcr_helper_GetFramePCR(pcr_helper, frame_data->dts);
if (current_pcr != VLC_TICK_INVALID)
*pcr = current_pcr;
vlc_list_remove(&frame_data->node);
free(frame_data);
}
if (*pcr != VLC_TICK_INVALID)
*pcr = __MIN(frame->i_dts, *pcr);
return VLC_SUCCESS;
}