mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 13:41:48 +00:00
2335 lines
79 KiB
C
2335 lines
79 KiB
C
/*
|
|
* mpegtspacketizer.c -
|
|
* Copyright (C) 2007, 2008 Alessandro Decina, Zaheer Merali
|
|
*
|
|
* Authors:
|
|
* Zaheer Merali <zaheerabbas at merali dot org>
|
|
* Alessandro Decina <alessandro@nnva.org>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
/* Skew calculation pameters */
|
|
#define MAX_TIME (2 * GST_SECOND)
|
|
|
|
/* maximal PCR time */
|
|
#define PCR_MAX_VALUE (((((guint64)1)<<33) * 300) + 298)
|
|
#define PCR_GST_MAX_VALUE (PCR_MAX_VALUE * GST_MSECOND / (PCR_MSECOND))
|
|
#define PTS_DTS_MAX_VALUE (((guint64)1) << 33)
|
|
|
|
#include "mpegtspacketizer.h"
|
|
#include "gstmpegdesc.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (mpegts_packetizer_debug);
|
|
#define GST_CAT_DEFAULT mpegts_packetizer_debug
|
|
|
|
static void _init_local (void);
|
|
G_DEFINE_TYPE_EXTENDED (MpegTSPacketizer2, mpegts_packetizer, G_TYPE_OBJECT, 0,
|
|
_init_local ());
|
|
|
|
#define ABSDIFF(a,b) ((a) < (b) ? (b) - (a) : (a) - (b))
|
|
|
|
static void mpegts_packetizer_dispose (GObject * object);
|
|
static void mpegts_packetizer_finalize (GObject * object);
|
|
static GstClockTime calculate_skew (MpegTSPCR * pcr, guint64 pcrtime,
|
|
GstClockTime time);
|
|
static void _close_current_group (MpegTSPCR * pcrtable);
|
|
static void record_pcr (MpegTSPacketizer2 * packetizer, MpegTSPCR * pcrtable,
|
|
guint64 pcr, guint64 offset);
|
|
|
|
#define CONTINUITY_UNSET 255
|
|
#define VERSION_NUMBER_UNSET 255
|
|
#define TABLE_ID_UNSET 0xFF
|
|
#define PACKET_SYNC_BYTE 0x47
|
|
|
|
static inline MpegTSPCR *
|
|
get_pcr_table (MpegTSPacketizer2 * packetizer, guint16 pid)
|
|
{
|
|
MpegTSPCR *res;
|
|
|
|
res = packetizer->observations[packetizer->pcrtablelut[pid]];
|
|
|
|
if (G_UNLIKELY (res == NULL)) {
|
|
/* If we don't have a PCR table for the requested PID, create one .. */
|
|
res = g_new0 (MpegTSPCR, 1);
|
|
/* Add it to the last table position */
|
|
packetizer->observations[packetizer->lastobsid] = res;
|
|
/* Update the pcrtablelut */
|
|
packetizer->pcrtablelut[pid] = packetizer->lastobsid;
|
|
/* And increment the last know slot */
|
|
packetizer->lastobsid++;
|
|
|
|
/* Finally set the default values */
|
|
res->pid = pid;
|
|
res->base_time = GST_CLOCK_TIME_NONE;
|
|
res->base_pcrtime = GST_CLOCK_TIME_NONE;
|
|
res->last_pcrtime = GST_CLOCK_TIME_NONE;
|
|
res->window_pos = 0;
|
|
res->window_filling = TRUE;
|
|
res->window_min = 0;
|
|
res->skew = 0;
|
|
res->prev_send_diff = GST_CLOCK_TIME_NONE;
|
|
res->prev_out_time = GST_CLOCK_TIME_NONE;
|
|
res->pcroffset = 0;
|
|
|
|
res->current = g_slice_new0 (PCROffsetCurrent);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
flush_observations (MpegTSPacketizer2 * packetizer)
|
|
{
|
|
gint i;
|
|
|
|
for (i = 0; i < packetizer->lastobsid; i++) {
|
|
g_free (packetizer->observations[i]);
|
|
packetizer->observations[i] = NULL;
|
|
}
|
|
memset (packetizer->pcrtablelut, 0xff, 0x2000);
|
|
packetizer->lastobsid = 0;
|
|
}
|
|
|
|
static inline MpegTSPacketizerStreamSubtable *
|
|
find_subtable (GSList * subtables, guint8 table_id, guint16 subtable_extension)
|
|
{
|
|
GSList *tmp;
|
|
|
|
/* FIXME: Make this an array ! */
|
|
for (tmp = subtables; tmp; tmp = tmp->next) {
|
|
MpegTSPacketizerStreamSubtable *sub =
|
|
(MpegTSPacketizerStreamSubtable *) tmp->data;
|
|
if (sub->table_id == table_id
|
|
&& sub->subtable_extension == subtable_extension)
|
|
return sub;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
seen_section_before (MpegTSPacketizerStream * stream, guint8 table_id,
|
|
guint16 subtable_extension, guint8 version_number, guint8 section_number,
|
|
guint8 last_section_number)
|
|
{
|
|
MpegTSPacketizerStreamSubtable *subtable;
|
|
|
|
/* Check if we've seen this table_id/subtable_extension first */
|
|
subtable = find_subtable (stream->subtables, table_id, subtable_extension);
|
|
if (!subtable) {
|
|
GST_DEBUG ("Haven't seen subtale");
|
|
return FALSE;
|
|
}
|
|
/* If we have, check it has the same version_number */
|
|
if (subtable->version_number != version_number) {
|
|
GST_DEBUG ("Different version number");
|
|
return FALSE;
|
|
}
|
|
/* Did the number of sections change ? */
|
|
if (subtable->last_section_number != last_section_number) {
|
|
GST_DEBUG ("Different last_section_number");
|
|
return FALSE;
|
|
}
|
|
/* Finally return whether we saw that section or not */
|
|
return MPEGTS_BIT_IS_SET (subtable->seen_section, section_number);
|
|
}
|
|
|
|
static MpegTSPacketizerStreamSubtable *
|
|
mpegts_packetizer_stream_subtable_new (guint8 table_id,
|
|
guint16 subtable_extension, guint8 last_section_number)
|
|
{
|
|
MpegTSPacketizerStreamSubtable *subtable;
|
|
|
|
subtable = g_new0 (MpegTSPacketizerStreamSubtable, 1);
|
|
subtable->version_number = VERSION_NUMBER_UNSET;
|
|
subtable->table_id = table_id;
|
|
subtable->subtable_extension = subtable_extension;
|
|
subtable->last_section_number = last_section_number;
|
|
return subtable;
|
|
}
|
|
|
|
static MpegTSPacketizerStream *
|
|
mpegts_packetizer_stream_new (guint16 pid)
|
|
{
|
|
MpegTSPacketizerStream *stream;
|
|
|
|
stream = (MpegTSPacketizerStream *) g_new0 (MpegTSPacketizerStream, 1);
|
|
stream->continuity_counter = CONTINUITY_UNSET;
|
|
stream->subtables = NULL;
|
|
stream->table_id = TABLE_ID_UNSET;
|
|
stream->pid = pid;
|
|
return stream;
|
|
}
|
|
|
|
static void
|
|
mpegts_packetizer_clear_section (MpegTSPacketizerStream * stream)
|
|
{
|
|
stream->continuity_counter = CONTINUITY_UNSET;
|
|
stream->section_length = 0;
|
|
stream->section_offset = 0;
|
|
stream->table_id = TABLE_ID_UNSET;
|
|
if (stream->section_data)
|
|
g_free (stream->section_data);
|
|
stream->section_data = NULL;
|
|
}
|
|
|
|
static void
|
|
mpegts_packetizer_stream_subtable_free (MpegTSPacketizerStreamSubtable *
|
|
subtable)
|
|
{
|
|
g_free (subtable);
|
|
}
|
|
|
|
static void
|
|
mpegts_packetizer_stream_free (MpegTSPacketizerStream * stream)
|
|
{
|
|
mpegts_packetizer_clear_section (stream);
|
|
if (stream->section_data)
|
|
g_free (stream->section_data);
|
|
g_slist_foreach (stream->subtables,
|
|
(GFunc) mpegts_packetizer_stream_subtable_free, NULL);
|
|
g_slist_free (stream->subtables);
|
|
g_free (stream);
|
|
}
|
|
|
|
static void
|
|
mpegts_packetizer_class_init (MpegTSPacketizer2Class * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->dispose = mpegts_packetizer_dispose;
|
|
gobject_class->finalize = mpegts_packetizer_finalize;
|
|
}
|
|
|
|
static void
|
|
mpegts_packetizer_init (MpegTSPacketizer2 * packetizer)
|
|
{
|
|
packetizer->adapter = gst_adapter_new ();
|
|
packetizer->offset = 0;
|
|
packetizer->empty = TRUE;
|
|
packetizer->streams = g_new0 (MpegTSPacketizerStream *, 8192);
|
|
packetizer->packet_size = 0;
|
|
packetizer->calculate_skew = FALSE;
|
|
packetizer->calculate_offset = FALSE;
|
|
|
|
packetizer->map_data = NULL;
|
|
packetizer->map_size = 0;
|
|
packetizer->map_offset = 0;
|
|
packetizer->need_sync = FALSE;
|
|
|
|
memset (packetizer->pcrtablelut, 0xff, 0x2000);
|
|
memset (packetizer->observations, 0x0, sizeof (packetizer->observations));
|
|
packetizer->lastobsid = 0;
|
|
|
|
packetizer->nb_seen_offsets = 0;
|
|
packetizer->refoffset = -1;
|
|
packetizer->last_in_time = GST_CLOCK_TIME_NONE;
|
|
}
|
|
|
|
static void
|
|
mpegts_packetizer_dispose (GObject * object)
|
|
{
|
|
MpegTSPacketizer2 *packetizer = GST_MPEGTS_PACKETIZER (object);
|
|
|
|
if (!packetizer->disposed) {
|
|
if (packetizer->packet_size)
|
|
packetizer->packet_size = 0;
|
|
if (packetizer->streams) {
|
|
int i;
|
|
for (i = 0; i < 8192; i++) {
|
|
if (packetizer->streams[i])
|
|
mpegts_packetizer_stream_free (packetizer->streams[i]);
|
|
}
|
|
g_free (packetizer->streams);
|
|
}
|
|
|
|
gst_adapter_clear (packetizer->adapter);
|
|
g_object_unref (packetizer->adapter);
|
|
packetizer->disposed = TRUE;
|
|
packetizer->offset = 0;
|
|
packetizer->empty = TRUE;
|
|
|
|
flush_observations (packetizer);
|
|
}
|
|
|
|
if (G_OBJECT_CLASS (mpegts_packetizer_parent_class)->dispose)
|
|
G_OBJECT_CLASS (mpegts_packetizer_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
mpegts_packetizer_finalize (GObject * object)
|
|
{
|
|
if (G_OBJECT_CLASS (mpegts_packetizer_parent_class)->finalize)
|
|
G_OBJECT_CLASS (mpegts_packetizer_parent_class)->finalize (object);
|
|
}
|
|
|
|
static inline guint64
|
|
mpegts_packetizer_compute_pcr (const guint8 * data)
|
|
{
|
|
guint32 pcr1;
|
|
guint16 pcr2;
|
|
guint64 pcr, pcr_ext;
|
|
|
|
pcr1 = GST_READ_UINT32_BE (data);
|
|
pcr2 = GST_READ_UINT16_BE (data + 4);
|
|
pcr = ((guint64) pcr1) << 1;
|
|
pcr |= (pcr2 & 0x8000) >> 15;
|
|
pcr_ext = (pcr2 & 0x01ff);
|
|
return pcr * 300 + pcr_ext % 300;
|
|
}
|
|
|
|
static gboolean
|
|
mpegts_packetizer_parse_adaptation_field_control (MpegTSPacketizer2 *
|
|
packetizer, MpegTSPacketizerPacket * packet)
|
|
{
|
|
guint8 length, afcflags;
|
|
guint8 *data;
|
|
|
|
length = *packet->data++;
|
|
|
|
/* an adaptation field with length 0 is valid and
|
|
* can be used to insert a single stuffing byte */
|
|
if (!length) {
|
|
packet->afc_flags = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
if ((packet->scram_afc_cc & 0x30) == 0x20) {
|
|
/* no payload, adaptation field of 183 bytes */
|
|
if (length > 183) {
|
|
GST_WARNING ("PID %d afc == 0x%02x and length %d > 183",
|
|
packet->pid, packet->scram_afc_cc & 0x30, length);
|
|
return FALSE;
|
|
}
|
|
if (length != 183) {
|
|
GST_WARNING ("PID %d afc == 0x%02x and length %d != 183",
|
|
packet->pid, packet->scram_afc_cc & 0x30, length);
|
|
GST_MEMDUMP ("Unknown payload", packet->data + length,
|
|
packet->data_end - packet->data - length);
|
|
}
|
|
} else if (length > 182) {
|
|
GST_WARNING ("PID %d afc == 0x%02x and length %d > 182",
|
|
packet->pid, packet->scram_afc_cc & 0x30, length);
|
|
return FALSE;
|
|
}
|
|
|
|
if (packet->data + length > packet->data_end) {
|
|
GST_DEBUG ("PID %d afc length %d overflows the buffer current %d max %d",
|
|
packet->pid, length, (gint) (packet->data - packet->data_start),
|
|
(gint) (packet->data_end - packet->data_start));
|
|
return FALSE;
|
|
}
|
|
|
|
data = packet->data;
|
|
packet->data += length;
|
|
|
|
afcflags = packet->afc_flags = *data++;
|
|
|
|
GST_DEBUG ("flags: %s%s%s%s%s%s%s%s%s",
|
|
afcflags & 0x80 ? "discontinuity " : "",
|
|
afcflags & 0x40 ? "random_access " : "",
|
|
afcflags & 0x20 ? "elementary_stream_priority " : "",
|
|
afcflags & 0x10 ? "PCR " : "",
|
|
afcflags & 0x08 ? "OPCR " : "",
|
|
afcflags & 0x04 ? "splicing_point " : "",
|
|
afcflags & 0x02 ? "transport_private_data " : "",
|
|
afcflags & 0x01 ? "extension " : "", afcflags == 0x00 ? "<none>" : "");
|
|
|
|
/* PCR */
|
|
if (afcflags & MPEGTS_AFC_PCR_FLAG) {
|
|
MpegTSPCR *pcrtable = NULL;
|
|
packet->pcr = mpegts_packetizer_compute_pcr (data);
|
|
data += 6;
|
|
GST_DEBUG ("pcr 0x%04x %" G_GUINT64_FORMAT " (%" GST_TIME_FORMAT
|
|
") offset:%" G_GUINT64_FORMAT, packet->pid, packet->pcr,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (packet->pcr)), packet->offset);
|
|
|
|
if (packetizer->calculate_skew
|
|
&& GST_CLOCK_TIME_IS_VALID (packetizer->last_in_time)) {
|
|
pcrtable = get_pcr_table (packetizer, packet->pid);
|
|
calculate_skew (pcrtable, packet->pcr, packetizer->last_in_time);
|
|
}
|
|
if (packetizer->calculate_offset) {
|
|
if (!pcrtable)
|
|
pcrtable = get_pcr_table (packetizer, packet->pid);
|
|
record_pcr (packetizer, pcrtable, packet->pcr, packet->offset);
|
|
}
|
|
}
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
/* OPCR */
|
|
if (afcflags & MPEGTS_AFC_OPCR_FLAG) {
|
|
/* Note: We don't use/need opcr for the time being */
|
|
guint64 opcr = mpegts_packetizer_compute_pcr (data);
|
|
data += 6;
|
|
GST_DEBUG ("opcr %" G_GUINT64_FORMAT " (%" GST_TIME_FORMAT ")",
|
|
opcr, GST_TIME_ARGS (PCRTIME_TO_GSTTIME (opcr)));
|
|
}
|
|
|
|
if (afcflags & MPEGTS_AFC_SPLICING_POINT_FLAG) {
|
|
GST_DEBUG ("splice_countdown: %u", *data++);
|
|
}
|
|
|
|
if (afcflags & MPEGTS_AFC_TRANSPORT_PRIVATE_DATA_FLAG) {
|
|
guint8 len = *data++;
|
|
GST_MEMDUMP ("private data", data, len);
|
|
data += len;
|
|
}
|
|
|
|
if (afcflags & MPEGTS_AFC_EXTENSION_FLAG) {
|
|
guint8 extlen = *data++;
|
|
guint8 flags = *data++;
|
|
GST_DEBUG ("extension size:%d flags : %s%s%s", extlen,
|
|
flags & 0x80 ? "ltw " : "",
|
|
flags & 0x40 ? "piecewise_rate " : "",
|
|
flags & 0x20 ? "seamless_splice " : "");
|
|
if (flags & 0x80) {
|
|
GST_DEBUG ("legal time window: valid_flag:%d offset:%d", *data >> 7,
|
|
GST_READ_UINT16_BE (data) & 0x7fff);
|
|
data += 2;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static MpegTSPacketizerPacketReturn
|
|
mpegts_packetizer_parse_packet (MpegTSPacketizer2 * packetizer,
|
|
MpegTSPacketizerPacket * packet)
|
|
{
|
|
guint8 *data;
|
|
guint8 tmp;
|
|
|
|
data = packet->data_start;
|
|
data += 1;
|
|
tmp = *data;
|
|
|
|
/* transport_error_indicator 1 */
|
|
if (G_UNLIKELY (tmp & 0x80))
|
|
return PACKET_BAD;
|
|
|
|
/* payload_unit_start_indicator 1 */
|
|
packet->payload_unit_start_indicator = tmp & 0x40;
|
|
|
|
/* transport_priority 1 */
|
|
/* PID 13 */
|
|
packet->pid = GST_READ_UINT16_BE (data) & 0x1FFF;
|
|
data += 2;
|
|
|
|
packet->scram_afc_cc = tmp = *data++;
|
|
/* transport_scrambling_control 2 */
|
|
if (G_UNLIKELY (tmp & 0xc0))
|
|
return PACKET_BAD;
|
|
|
|
packet->data = data;
|
|
|
|
if (FLAGS_HAS_AFC (tmp))
|
|
if (!mpegts_packetizer_parse_adaptation_field_control (packetizer, packet))
|
|
return FALSE;
|
|
|
|
if (FLAGS_HAS_PAYLOAD (tmp))
|
|
packet->payload = packet->data;
|
|
else
|
|
packet->payload = NULL;
|
|
|
|
return PACKET_OK;
|
|
}
|
|
|
|
static GstMpegTsSection *
|
|
mpegts_packetizer_parse_section_header (MpegTSPacketizer2 * packetizer,
|
|
MpegTSPacketizerStream * stream)
|
|
{
|
|
MpegTSPacketizerStreamSubtable *subtable;
|
|
GstMpegTsSection *res;
|
|
|
|
subtable =
|
|
find_subtable (stream->subtables, stream->table_id,
|
|
stream->subtable_extension);
|
|
if (subtable) {
|
|
GST_DEBUG ("Found previous subtable_extension:0x%04x",
|
|
stream->subtable_extension);
|
|
if (G_UNLIKELY (stream->version_number != subtable->version_number)) {
|
|
/* If the version number changed, reset the subtable */
|
|
subtable->version_number = stream->version_number;
|
|
subtable->last_section_number = stream->last_section_number;
|
|
memset (subtable->seen_section, 0, 32);
|
|
}
|
|
} else {
|
|
GST_DEBUG ("Appending new subtable_extension: 0x%04x",
|
|
stream->subtable_extension);
|
|
subtable = mpegts_packetizer_stream_subtable_new (stream->table_id,
|
|
stream->subtable_extension, stream->last_section_number);
|
|
subtable->version_number = stream->version_number;
|
|
|
|
stream->subtables = g_slist_prepend (stream->subtables, subtable);
|
|
}
|
|
|
|
GST_MEMDUMP ("Full section data", stream->section_data,
|
|
stream->section_length);
|
|
/* TODO ? : Replace this by an efficient version (where we provide all
|
|
* pre-parsed header data) */
|
|
res =
|
|
gst_mpegts_section_new (stream->pid, stream->section_data,
|
|
stream->section_length);
|
|
stream->section_data = NULL;
|
|
mpegts_packetizer_clear_section (stream);
|
|
|
|
if (res) {
|
|
/* NOTE : Due to the new mpegts-si system, There is a insanely low probability
|
|
* that we might have gotten a section that was corrupted (i.e. wrong crc)
|
|
* and that we consider it as seen.
|
|
*
|
|
* The reason why we consider this as acceptable is because all the previous
|
|
* checks were already done:
|
|
* * transport layer checks (DVB)
|
|
* * 0x47 validation
|
|
* * continuity counter validation
|
|
* * subtable validation
|
|
* * section_number validation
|
|
* * section_length validation
|
|
*
|
|
* The probability of this happening vs the overhead of doing CRC checks
|
|
* on all sections (including those we would not use) is just not worth it.
|
|
* */
|
|
MPEGTS_BIT_SET (subtable->seen_section, stream->section_number);
|
|
res->offset = stream->offset;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
void
|
|
mpegts_packetizer_clear (MpegTSPacketizer2 * packetizer)
|
|
{
|
|
guint i;
|
|
|
|
if (packetizer->packet_size)
|
|
packetizer->packet_size = 0;
|
|
|
|
if (packetizer->streams) {
|
|
int i;
|
|
for (i = 0; i < 8192; i++) {
|
|
if (packetizer->streams[i]) {
|
|
mpegts_packetizer_stream_free (packetizer->streams[i]);
|
|
}
|
|
}
|
|
memset (packetizer->streams, 0, 8192 * sizeof (MpegTSPacketizerStream *));
|
|
}
|
|
|
|
gst_adapter_clear (packetizer->adapter);
|
|
packetizer->offset = 0;
|
|
packetizer->empty = TRUE;
|
|
packetizer->need_sync = FALSE;
|
|
packetizer->map_data = NULL;
|
|
packetizer->map_size = 0;
|
|
packetizer->map_offset = 0;
|
|
packetizer->last_in_time = GST_CLOCK_TIME_NONE;
|
|
|
|
/* Close current PCR group */
|
|
for (i = 0; i < MAX_PCR_OBS_CHANNELS; i++) {
|
|
if (packetizer->observations[i])
|
|
_close_current_group (packetizer->observations[i]);
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
mpegts_packetizer_flush (MpegTSPacketizer2 * packetizer, gboolean hard)
|
|
{
|
|
guint i;
|
|
GST_DEBUG ("Flushing");
|
|
|
|
if (packetizer->streams) {
|
|
for (i = 0; i < 8192; i++) {
|
|
if (packetizer->streams[i]) {
|
|
mpegts_packetizer_clear_section (packetizer->streams[i]);
|
|
}
|
|
}
|
|
}
|
|
gst_adapter_clear (packetizer->adapter);
|
|
|
|
packetizer->offset = 0;
|
|
packetizer->empty = TRUE;
|
|
packetizer->need_sync = FALSE;
|
|
packetizer->map_data = NULL;
|
|
packetizer->map_size = 0;
|
|
packetizer->map_offset = 0;
|
|
packetizer->last_in_time = GST_CLOCK_TIME_NONE;
|
|
|
|
/* Close current PCR group */
|
|
for (i = 0; i < MAX_PCR_OBS_CHANNELS; i++) {
|
|
if (packetizer->observations[i])
|
|
_close_current_group (packetizer->observations[i]);
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (hard) {
|
|
/* For pull mode seeks in tsdemux the observation must be preserved */
|
|
flush_observations (packetizer);
|
|
}
|
|
}
|
|
|
|
void
|
|
mpegts_packetizer_remove_stream (MpegTSPacketizer2 * packetizer, gint16 pid)
|
|
{
|
|
MpegTSPacketizerStream *stream = packetizer->streams[pid];
|
|
if (stream) {
|
|
GST_INFO ("Removing stream for PID %d", pid);
|
|
mpegts_packetizer_stream_free (stream);
|
|
packetizer->streams[pid] = NULL;
|
|
}
|
|
}
|
|
|
|
MpegTSPacketizer2 *
|
|
mpegts_packetizer_new (void)
|
|
{
|
|
MpegTSPacketizer2 *packetizer;
|
|
|
|
packetizer =
|
|
GST_MPEGTS_PACKETIZER (g_object_new (GST_TYPE_MPEGTS_PACKETIZER, NULL));
|
|
|
|
return packetizer;
|
|
}
|
|
|
|
void
|
|
mpegts_packetizer_push (MpegTSPacketizer2 * packetizer, GstBuffer * buffer)
|
|
{
|
|
if (G_UNLIKELY (packetizer->empty)) {
|
|
packetizer->empty = FALSE;
|
|
packetizer->offset = GST_BUFFER_OFFSET (buffer);
|
|
}
|
|
|
|
GST_DEBUG ("Pushing %" G_GSIZE_FORMAT " byte from offset %"
|
|
G_GUINT64_FORMAT, gst_buffer_get_size (buffer),
|
|
GST_BUFFER_OFFSET (buffer));
|
|
gst_adapter_push (packetizer->adapter, buffer);
|
|
/* If buffer timestamp is valid, store it */
|
|
if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (buffer)))
|
|
packetizer->last_in_time = GST_BUFFER_TIMESTAMP (buffer);
|
|
}
|
|
|
|
static void
|
|
mpegts_packetizer_flush_bytes (MpegTSPacketizer2 * packetizer, gsize size)
|
|
{
|
|
if (size > 0) {
|
|
GST_LOG ("flushing %" G_GSIZE_FORMAT " bytes from adapter", size);
|
|
gst_adapter_flush (packetizer->adapter, size);
|
|
}
|
|
|
|
packetizer->map_data = NULL;
|
|
packetizer->map_size = 0;
|
|
packetizer->map_offset = 0;
|
|
}
|
|
|
|
static gboolean
|
|
mpegts_packetizer_map (MpegTSPacketizer2 * packetizer, gsize size)
|
|
{
|
|
gsize available;
|
|
|
|
if (packetizer->map_size - packetizer->map_offset >= size)
|
|
return TRUE;
|
|
|
|
mpegts_packetizer_flush_bytes (packetizer, packetizer->map_offset);
|
|
|
|
available = gst_adapter_available (packetizer->adapter);
|
|
if (available < size)
|
|
return FALSE;
|
|
|
|
packetizer->map_data =
|
|
(guint8 *) gst_adapter_map (packetizer->adapter, available);
|
|
if (!packetizer->map_data)
|
|
return FALSE;
|
|
|
|
packetizer->map_size = available;
|
|
packetizer->map_offset = 0;
|
|
|
|
GST_LOG ("mapped %" G_GSIZE_FORMAT " bytes from adapter", available);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
mpegts_try_discover_packet_size (MpegTSPacketizer2 * packetizer)
|
|
{
|
|
guint8 *data;
|
|
gsize size, i, j;
|
|
|
|
static const guint psizes[] = {
|
|
MPEGTS_NORMAL_PACKETSIZE,
|
|
MPEGTS_M2TS_PACKETSIZE,
|
|
MPEGTS_DVB_ASI_PACKETSIZE,
|
|
MPEGTS_ATSC_PACKETSIZE
|
|
};
|
|
|
|
if (!mpegts_packetizer_map (packetizer, 4 * MPEGTS_MAX_PACKETSIZE))
|
|
return FALSE;
|
|
|
|
size = packetizer->map_size - packetizer->map_offset;
|
|
data = packetizer->map_data + packetizer->map_offset;
|
|
|
|
for (i = 0; i + 3 * MPEGTS_MAX_PACKETSIZE < size; i++) {
|
|
/* find a sync byte */
|
|
if (data[i] != PACKET_SYNC_BYTE)
|
|
continue;
|
|
|
|
/* check for 4 consecutive sync bytes with each possible packet size */
|
|
for (j = 0; j < G_N_ELEMENTS (psizes); j++) {
|
|
guint packet_size = psizes[j];
|
|
|
|
if (data[i + packet_size] == PACKET_SYNC_BYTE &&
|
|
data[i + 2 * packet_size] == PACKET_SYNC_BYTE &&
|
|
data[i + 3 * packet_size] == PACKET_SYNC_BYTE) {
|
|
packetizer->packet_size = packet_size;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
packetizer->map_offset += i;
|
|
|
|
if (packetizer->packet_size == 0) {
|
|
GST_DEBUG ("Could not determine packet size in %" G_GSIZE_FORMAT
|
|
" bytes buffer, flush %" G_GSIZE_FORMAT " bytes", size, i);
|
|
mpegts_packetizer_flush_bytes (packetizer, packetizer->map_offset);
|
|
return FALSE;
|
|
}
|
|
|
|
GST_INFO ("have packetsize detected: %u bytes", packetizer->packet_size);
|
|
|
|
if (packetizer->packet_size == MPEGTS_M2TS_PACKETSIZE &&
|
|
packetizer->map_offset >= 4)
|
|
packetizer->map_offset -= 4;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
mpegts_packetizer_sync (MpegTSPacketizer2 * packetizer)
|
|
{
|
|
gboolean found = FALSE;
|
|
guint8 *data;
|
|
guint packet_size;
|
|
gsize size, sync_offset, i;
|
|
|
|
packet_size = packetizer->packet_size;
|
|
|
|
if (!mpegts_packetizer_map (packetizer, 3 * packet_size))
|
|
return FALSE;
|
|
|
|
size = packetizer->map_size - packetizer->map_offset;
|
|
data = packetizer->map_data + packetizer->map_offset;
|
|
|
|
if (packet_size == MPEGTS_M2TS_PACKETSIZE)
|
|
sync_offset = 4;
|
|
else
|
|
sync_offset = 0;
|
|
|
|
for (i = sync_offset; i + 2 * packet_size < size; i++) {
|
|
if (data[i] == PACKET_SYNC_BYTE &&
|
|
data[i + packet_size] == PACKET_SYNC_BYTE &&
|
|
data[i + 2 * packet_size] == PACKET_SYNC_BYTE) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
packetizer->map_offset += i - sync_offset;
|
|
|
|
if (!found)
|
|
mpegts_packetizer_flush_bytes (packetizer, packetizer->map_offset);
|
|
|
|
return found;
|
|
}
|
|
|
|
MpegTSPacketizerPacketReturn
|
|
mpegts_packetizer_next_packet (MpegTSPacketizer2 * packetizer,
|
|
MpegTSPacketizerPacket * packet)
|
|
{
|
|
guint8 *packet_data;
|
|
guint packet_size;
|
|
gsize sync_offset;
|
|
|
|
packet_size = packetizer->packet_size;
|
|
if (G_UNLIKELY (!packet_size)) {
|
|
if (!mpegts_try_discover_packet_size (packetizer))
|
|
return PACKET_NEED_MORE;
|
|
packet_size = packetizer->packet_size;
|
|
}
|
|
|
|
/* M2TS packets don't start with the sync byte, all other variants do */
|
|
if (packet_size == MPEGTS_M2TS_PACKETSIZE)
|
|
sync_offset = 4;
|
|
else
|
|
sync_offset = 0;
|
|
|
|
while (1) {
|
|
if (packetizer->need_sync) {
|
|
if (!mpegts_packetizer_sync (packetizer))
|
|
return PACKET_NEED_MORE;
|
|
packetizer->need_sync = FALSE;
|
|
}
|
|
|
|
if (!mpegts_packetizer_map (packetizer, packet_size))
|
|
return PACKET_NEED_MORE;
|
|
|
|
packet_data = &packetizer->map_data[packetizer->map_offset + sync_offset];
|
|
|
|
/* Check sync byte */
|
|
if (G_UNLIKELY (*packet_data != PACKET_SYNC_BYTE)) {
|
|
GST_DEBUG ("lost sync");
|
|
packetizer->need_sync = TRUE;
|
|
} else {
|
|
/* ALL mpeg-ts variants contain 188 bytes of data. Those with bigger
|
|
* packet sizes contain either extra data (timesync, FEC, ..) either
|
|
* before or after the data */
|
|
packet->data_start = packet_data;
|
|
packet->data_end = packet->data_start + 188;
|
|
packet->offset = packetizer->offset;
|
|
GST_LOG ("offset %" G_GUINT64_FORMAT, packet->offset);
|
|
packetizer->offset += packet_size;
|
|
GST_MEMDUMP ("data_start", packet->data_start, 16);
|
|
|
|
return mpegts_packetizer_parse_packet (packetizer, packet);
|
|
}
|
|
}
|
|
}
|
|
|
|
MpegTSPacketizerPacketReturn
|
|
mpegts_packetizer_process_next_packet (MpegTSPacketizer2 * packetizer)
|
|
{
|
|
MpegTSPacketizerPacket packet;
|
|
MpegTSPacketizerPacketReturn ret;
|
|
|
|
ret = mpegts_packetizer_next_packet (packetizer, &packet);
|
|
if (ret != PACKET_NEED_MORE)
|
|
mpegts_packetizer_clear_packet (packetizer, &packet);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
mpegts_packetizer_clear_packet (MpegTSPacketizer2 * packetizer,
|
|
MpegTSPacketizerPacket * packet)
|
|
{
|
|
guint8 packet_size = packetizer->packet_size;
|
|
|
|
if (packetizer->map_data) {
|
|
packetizer->map_offset += packet_size;
|
|
if (packetizer->map_size - packetizer->map_offset < packet_size)
|
|
mpegts_packetizer_flush_bytes (packetizer, packetizer->map_offset);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
mpegts_packetizer_has_packets (MpegTSPacketizer2 * packetizer)
|
|
{
|
|
if (G_UNLIKELY (!packetizer->packet_size)) {
|
|
if (!mpegts_try_discover_packet_size (packetizer))
|
|
return FALSE;
|
|
}
|
|
return gst_adapter_available (packetizer->adapter) >= packetizer->packet_size;
|
|
}
|
|
|
|
/*
|
|
* Ideally it should just return a section if:
|
|
* * The section is complete
|
|
* * The section is valid (sanity checks for length for example)
|
|
* * The section applies now (current_next_indicator)
|
|
* * The section is an update or was never seen
|
|
*
|
|
* The section should be a new GstMpegTsSection:
|
|
* * properly initialized
|
|
* * With pid, table_id AND section_type set (move logic from mpegtsbase)
|
|
* * With data copied into it (yes, minor overhead)
|
|
*
|
|
* In all other cases it should just return NULL
|
|
*
|
|
* If more than one section is available, the 'remaining' field will
|
|
* be set to the beginning of a valid GList containing other sections.
|
|
* */
|
|
GstMpegTsSection *
|
|
mpegts_packetizer_push_section (MpegTSPacketizer2 * packetizer,
|
|
MpegTSPacketizerPacket * packet, GList ** remaining)
|
|
{
|
|
GstMpegTsSection *section;
|
|
GstMpegTsSection *res = NULL;
|
|
MpegTSPacketizerStream *stream;
|
|
gboolean long_packet;
|
|
guint8 pointer = 0, table_id;
|
|
guint16 subtable_extension = 0;
|
|
gsize to_read;
|
|
guint section_length;
|
|
/* data points to the current read location
|
|
* data_start points to the beginning of the data to accumulate */
|
|
guint8 *data, *data_start;
|
|
guint8 packet_cc;
|
|
GList *others = NULL;
|
|
guint8 version_number, section_number, last_section_number;
|
|
|
|
data = packet->data;
|
|
packet_cc = FLAGS_CONTINUITY_COUNTER (packet->scram_afc_cc);
|
|
|
|
/* Get our filter */
|
|
stream = packetizer->streams[packet->pid];
|
|
if (G_UNLIKELY (stream == NULL)) {
|
|
if (!packet->payload_unit_start_indicator) {
|
|
/* Early exit (we need to start with a section start) */
|
|
GST_DEBUG ("PID 0x%04x waiting for section start", packet->pid);
|
|
goto out;
|
|
}
|
|
stream = mpegts_packetizer_stream_new (packet->pid);
|
|
packetizer->streams[packet->pid] = stream;
|
|
}
|
|
|
|
GST_MEMDUMP ("Full packet data", packet->data,
|
|
packet->data_end - packet->data);
|
|
|
|
/* This function is split into several parts:
|
|
*
|
|
* Pre checks (packet-wide). Determines where we go next
|
|
* accumulate_data: store data and check if section is complete
|
|
* section_start: handle beginning of a section, if needed loop back to
|
|
* accumulate_data
|
|
*
|
|
* The trigger that makes the loop stop and return is if:
|
|
* 1) We do not have enough data for the current packet
|
|
* 2) There is remaining data after a packet which is only made
|
|
* of stuffing bytes (0xff).
|
|
*
|
|
* Pre-loop checks, related to the whole incoming packet:
|
|
*
|
|
* If there is a CC-discont:
|
|
* If it is a PUSI, skip the pointer and handle section_start
|
|
* If not a PUSI, reset and return nothing
|
|
* If there is not a CC-discont:
|
|
* If it is a PUSI
|
|
* If pointer, accumulate that data and check for complete section
|
|
* (loop)
|
|
* If it is not a PUSI
|
|
* Accumulate the expected data and check for complete section
|
|
* (loop)
|
|
*
|
|
**/
|
|
|
|
if (packet->payload_unit_start_indicator) {
|
|
pointer = *data++;
|
|
/* If the pointer is zero, we're guaranteed to be able to handle it */
|
|
if (pointer == 0) {
|
|
GST_LOG
|
|
("PID 0x%04x PUSI and pointer == 0, skipping straight to section_start parsing",
|
|
packet->pid);
|
|
goto section_start;
|
|
}
|
|
}
|
|
|
|
if (stream->continuity_counter == CONTINUITY_UNSET ||
|
|
(stream->continuity_counter + 1) % 16 != packet_cc) {
|
|
if (stream->continuity_counter != CONTINUITY_UNSET)
|
|
GST_WARNING ("PID 0x%04x section discontinuity (%d vs %d)", packet->pid,
|
|
stream->continuity_counter, packet_cc);
|
|
mpegts_packetizer_clear_section (stream);
|
|
/* If not a PUSI, not much we can do */
|
|
if (!packet->payload_unit_start_indicator) {
|
|
GST_LOG ("PID 0x%04x continuity discont/unset and not PUSI, bailing out",
|
|
packet->pid);
|
|
goto out;
|
|
}
|
|
/* If PUSI, skip pointer data and carry on to section start */
|
|
data += pointer;
|
|
pointer = 0;
|
|
GST_LOG ("discont, but PUSI, skipped %d bytes and doing section start",
|
|
pointer);
|
|
goto section_start;
|
|
}
|
|
|
|
GST_LOG ("Accumulating data from beginning of packet");
|
|
|
|
data_start = data;
|
|
|
|
accumulate_data:
|
|
/* If not the beginning of a new section, accumulate what we have */
|
|
stream->continuity_counter = packet_cc;
|
|
to_read = MIN (stream->section_length - stream->section_offset,
|
|
packet->data_end - data_start);
|
|
memcpy (stream->section_data + stream->section_offset, data_start, to_read);
|
|
stream->section_offset += to_read;
|
|
/* Point data to after the data we accumulated */
|
|
data = data_start + to_read;
|
|
GST_DEBUG ("Appending data (need %d, have %d)", stream->section_length,
|
|
stream->section_offset);
|
|
|
|
/* Check if we have enough */
|
|
if (stream->section_offset < stream->section_length) {
|
|
GST_DEBUG ("PID 0x%04x, section not complete (Got %d, need %d)",
|
|
stream->pid, stream->section_offset, stream->section_length);
|
|
goto out;
|
|
}
|
|
|
|
/* Small sanity check. We should have collected *exactly* the right amount */
|
|
if (G_UNLIKELY (stream->section_offset != stream->section_length))
|
|
GST_WARNING ("PID 0x%04x Accumulated too much data (%d vs %d) !",
|
|
stream->pid, stream->section_offset, stream->section_length);
|
|
GST_DEBUG ("PID 0x%04x Section complete", stream->pid);
|
|
|
|
if ((section = mpegts_packetizer_parse_section_header (packetizer, stream))) {
|
|
if (res)
|
|
others = g_list_append (others, section);
|
|
else
|
|
res = section;
|
|
}
|
|
|
|
/* FIXME : We need at least 8 bytes with current algorithm :(
|
|
* We might end up losing sections that start across two packets (srsl...) */
|
|
if (data > packet->data_end - 8 || *data == 0xff) {
|
|
/* flush stuffing bytes and leave */
|
|
mpegts_packetizer_clear_section (stream);
|
|
goto out;
|
|
}
|
|
|
|
/* We have more data to process ... */
|
|
GST_DEBUG ("PID 0x%04x, More section present in packet (remaining bytes:%"
|
|
G_GSIZE_FORMAT ")", stream->pid, packet->data_end - data);
|
|
|
|
section_start:
|
|
GST_MEMDUMP ("section_start", data, packet->data_end - data);
|
|
data_start = data;
|
|
/* Beginning of a new section */
|
|
/*
|
|
* section_syntax_indicator means that the header is of the following format:
|
|
* * table_id (8bit)
|
|
* * section_syntax_indicator (1bit) == 0
|
|
* * reserved/private fields (3bit)
|
|
* * section_length (12bit)
|
|
* * data (of size section_length)
|
|
* * NO CRC !
|
|
*/
|
|
long_packet = data[1] & 0x80;
|
|
|
|
/* Fast path for short packets */
|
|
if (!long_packet) {
|
|
/* We can create the section now (function will check for size) */
|
|
GST_DEBUG ("Short packet");
|
|
section_length = (GST_READ_UINT16_BE (data + 1) & 0xfff) + 3;
|
|
/* Only do fast-path if we have enough byte */
|
|
if (section_length < packet->data_end - data) {
|
|
if ((section =
|
|
gst_mpegts_section_new (packet->pid, g_memdup (data,
|
|
section_length), section_length))) {
|
|
GST_DEBUG ("PID 0x%04x Short section complete !", packet->pid);
|
|
section->offset = packet->offset;
|
|
if (res)
|
|
others = g_list_append (others, section);
|
|
else
|
|
res = section;
|
|
}
|
|
/* Advance reader and potentially read another section */
|
|
data += section_length;
|
|
if (data < packet->data_end && *data != 0xff)
|
|
goto section_start;
|
|
/* If not, exit */
|
|
goto out;
|
|
}
|
|
/* We don't have enough bytes to do short section shortcut */
|
|
}
|
|
|
|
/* Beginning of a new section, do as much pre-parsing as possible */
|
|
/* table_id : 8 bit */
|
|
table_id = *data++;
|
|
|
|
/* section_syntax_indicator : 1 bit
|
|
* other_fields (reserved) : 3 bit
|
|
* section_length : 12 bit */
|
|
section_length = (GST_READ_UINT16_BE (data) & 0x0FFF) + 3;
|
|
data += 2;
|
|
|
|
if (long_packet) {
|
|
/* subtable_extension (always present, we are in a long section) */
|
|
/* subtable extension : 16 bit */
|
|
subtable_extension = GST_READ_UINT16_BE (data);
|
|
data += 2;
|
|
|
|
/* reserved : 2 bit
|
|
* version_number : 5 bit
|
|
* current_next_indicator : 1 bit */
|
|
/* Bail out now if current_next_indicator == 0 */
|
|
if (G_UNLIKELY (!(*data & 0x01))) {
|
|
GST_DEBUG
|
|
("PID 0x%04x table_id 0x%02x section does not apply (current_next_indicator == 0)",
|
|
packet->pid, table_id);
|
|
goto out;
|
|
}
|
|
|
|
version_number = *data++ >> 1 & 0x1f;
|
|
/* section_number : 8 bit */
|
|
section_number = *data++;
|
|
/* last_section_number : 8 bit */
|
|
last_section_number = *data++;
|
|
} else {
|
|
subtable_extension = 0;
|
|
version_number = 0;
|
|
section_number = 0;
|
|
last_section_number = 0;
|
|
}
|
|
GST_DEBUG
|
|
("PID 0x%04x length:%d table_id:0x%02x subtable_extension:0x%04x version_number:%d section_number:%d(last:%d)",
|
|
packet->pid, section_length, table_id, subtable_extension, version_number,
|
|
section_number, last_section_number);
|
|
|
|
to_read = MIN (section_length, packet->data_end - data_start);
|
|
|
|
/* Check as early as possible whether we already saw this section
|
|
* i.e. that we saw a subtable with:
|
|
* * same subtable_extension (might be zero)
|
|
* * same version_number
|
|
* * same last_section_number
|
|
* * same section_number was seen
|
|
*/
|
|
if (seen_section_before (stream, table_id, subtable_extension,
|
|
version_number, section_number, last_section_number)) {
|
|
GST_DEBUG
|
|
("PID 0x%04x Already processed table_id:0x%02x subtable_extension:0x%04x, version_number:%d, section_number:%d",
|
|
packet->pid, table_id, subtable_extension, version_number,
|
|
section_number);
|
|
/* skip data and see if we have more sections after */
|
|
data = data_start + to_read;
|
|
if (data == packet->data_end || *data == 0xff)
|
|
goto out;
|
|
goto section_start;
|
|
}
|
|
if (G_UNLIKELY (section_number > last_section_number)) {
|
|
GST_WARNING
|
|
("PID 0x%04x corrupted packet (section_number:%d > last_section_number:%d)",
|
|
packet->pid, section_number, last_section_number);
|
|
goto out;
|
|
}
|
|
|
|
|
|
/* Copy over already parsed values */
|
|
stream->table_id = table_id;
|
|
stream->section_length = section_length;
|
|
stream->version_number = version_number;
|
|
stream->subtable_extension = subtable_extension;
|
|
stream->section_number = section_number;
|
|
stream->last_section_number = last_section_number;
|
|
stream->offset = packet->offset;
|
|
|
|
/* Create enough room to store chunks of sections */
|
|
stream->section_data = g_malloc (stream->section_length);
|
|
stream->section_offset = 0;
|
|
|
|
/* Finally, accumulate and check if we parsed enough */
|
|
goto accumulate_data;
|
|
|
|
out:
|
|
packet->data = data;
|
|
*remaining = others;
|
|
|
|
GST_DEBUG ("result: %p", res);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void
|
|
_init_local (void)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (mpegts_packetizer_debug, "mpegtspacketizer", 0,
|
|
"MPEG transport stream parser");
|
|
}
|
|
|
|
|
|
static void
|
|
mpegts_packetizer_resync (MpegTSPCR * pcr, GstClockTime time,
|
|
GstClockTime gstpcrtime, gboolean reset_skew)
|
|
{
|
|
pcr->base_time = time;
|
|
pcr->base_pcrtime = gstpcrtime;
|
|
pcr->prev_out_time = GST_CLOCK_TIME_NONE;
|
|
pcr->prev_send_diff = GST_CLOCK_TIME_NONE;
|
|
if (reset_skew) {
|
|
pcr->window_filling = TRUE;
|
|
pcr->window_pos = 0;
|
|
pcr->window_min = 0;
|
|
pcr->window_size = 0;
|
|
pcr->skew = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/* Code mostly copied from -good/gst/rtpmanager/rtpjitterbuffer.c */
|
|
|
|
/* For the clock skew we use a windowed low point averaging algorithm as can be
|
|
* found in Fober, Orlarey and Letz, 2005, "Real Time Clock Skew Estimation
|
|
* over Network Delays":
|
|
* http://www.grame.fr/Ressources/pub/TR-050601.pdf
|
|
* http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.102.1546
|
|
*
|
|
* The idea is that the jitter is composed of:
|
|
*
|
|
* J = N + n
|
|
*
|
|
* N : a constant network delay.
|
|
* n : random added noise. The noise is concentrated around 0
|
|
*
|
|
* In the receiver we can track the elapsed time at the sender with:
|
|
*
|
|
* send_diff(i) = (Tsi - Ts0);
|
|
*
|
|
* Tsi : The time at the sender at packet i
|
|
* Ts0 : The time at the sender at the first packet
|
|
*
|
|
* This is the difference between the RTP timestamp in the first received packet
|
|
* and the current packet.
|
|
*
|
|
* At the receiver we have to deal with the jitter introduced by the network.
|
|
*
|
|
* recv_diff(i) = (Tri - Tr0)
|
|
*
|
|
* Tri : The time at the receiver at packet i
|
|
* Tr0 : The time at the receiver at the first packet
|
|
*
|
|
* Both of these values contain a jitter Ji, a jitter for packet i, so we can
|
|
* write:
|
|
*
|
|
* recv_diff(i) = (Cri + D + ni) - (Cr0 + D + n0))
|
|
*
|
|
* Cri : The time of the clock at the receiver for packet i
|
|
* D + ni : The jitter when receiving packet i
|
|
*
|
|
* We see that the network delay is irrelevant here as we can elliminate D:
|
|
*
|
|
* recv_diff(i) = (Cri + ni) - (Cr0 + n0))
|
|
*
|
|
* The drift is now expressed as:
|
|
*
|
|
* Drift(i) = recv_diff(i) - send_diff(i);
|
|
*
|
|
* We now keep the W latest values of Drift and find the minimum (this is the
|
|
* one with the lowest network jitter and thus the one which is least affected
|
|
* by it). We average this lowest value to smooth out the resulting network skew.
|
|
*
|
|
* Both the window and the weighting used for averaging influence the accuracy
|
|
* of the drift estimation. Finding the correct parameters turns out to be a
|
|
* compromise between accuracy and inertia.
|
|
*
|
|
* We use a 2 second window or up to 512 data points, which is statistically big
|
|
* enough to catch spikes (FIXME, detect spikes).
|
|
* We also use a rather large weighting factor (125) to smoothly adapt. During
|
|
* startup, when filling the window, we use a parabolic weighting factor, the
|
|
* more the window is filled, the faster we move to the detected possible skew.
|
|
*
|
|
* Returns: @time adjusted with the clock skew.
|
|
*/
|
|
static GstClockTime
|
|
calculate_skew (MpegTSPCR * pcr, guint64 pcrtime, GstClockTime time)
|
|
{
|
|
guint64 send_diff, recv_diff;
|
|
gint64 delta;
|
|
gint64 old;
|
|
gint pos, i;
|
|
GstClockTime gstpcrtime, out_time;
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
guint64 slope;
|
|
#endif
|
|
|
|
gstpcrtime = PCRTIME_TO_GSTTIME (pcrtime) + pcr->pcroffset;
|
|
|
|
/* first time, lock on to time and gstpcrtime */
|
|
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (pcr->base_time))) {
|
|
pcr->base_time = time;
|
|
pcr->prev_out_time = GST_CLOCK_TIME_NONE;
|
|
GST_DEBUG ("Taking new base time %" GST_TIME_FORMAT, GST_TIME_ARGS (time));
|
|
}
|
|
|
|
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (pcr->base_pcrtime))) {
|
|
pcr->base_pcrtime = gstpcrtime;
|
|
pcr->prev_send_diff = -1;
|
|
GST_DEBUG ("Taking new base pcrtime %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (gstpcrtime));
|
|
}
|
|
|
|
/* Handle PCR wraparound and resets */
|
|
if (GST_CLOCK_TIME_IS_VALID (pcr->last_pcrtime) &&
|
|
gstpcrtime < pcr->last_pcrtime) {
|
|
if (pcr->last_pcrtime - gstpcrtime > PCR_GST_MAX_VALUE / 2) {
|
|
/* PCR wraparound */
|
|
GST_DEBUG ("PCR wrap");
|
|
pcr->pcroffset += PCR_GST_MAX_VALUE;
|
|
gstpcrtime = PCRTIME_TO_GSTTIME (pcrtime) + pcr->pcroffset;
|
|
send_diff = gstpcrtime - pcr->base_pcrtime;
|
|
} else if (GST_CLOCK_TIME_IS_VALID (time)
|
|
&& pcr->last_pcrtime - gstpcrtime > 15 * GST_SECOND) {
|
|
/* Assume a reset */
|
|
GST_DEBUG ("PCR reset");
|
|
/* Calculate PCR we would have expected for the given input time,
|
|
* essentially applying the reverse correction process
|
|
*
|
|
* We want to find the PCR offset to apply
|
|
* pcroffset = (corrected) gstpcrtime - (received) gstpcrtime
|
|
*
|
|
* send_diff = (corrected) gstpcrtime - pcr->base_pcrtime
|
|
* recv_diff = time - pcr->base_time
|
|
* out_time = pcr->base_time + send_diff
|
|
*
|
|
* We are assuming that send_diff == recv_diff
|
|
* (corrected) gstpcrtime - pcr->base_pcrtime = time - pcr->base_time
|
|
* Giving us:
|
|
* (corrected) gstpcrtime = time - pcr->base_time + pcr->base_pcrtime
|
|
*
|
|
* And therefore:
|
|
* pcroffset = time - pcr->base_time + pcr->base_pcrtime - (received) gstpcrtime
|
|
**/
|
|
pcr->pcroffset += time - pcr->base_time + pcr->base_pcrtime - gstpcrtime;
|
|
gstpcrtime = PCRTIME_TO_GSTTIME (pcrtime) + pcr->pcroffset;
|
|
send_diff = gstpcrtime - pcr->base_pcrtime;
|
|
GST_DEBUG ("Introduced offset is now %" GST_TIME_FORMAT
|
|
" corrected pcr time %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (pcr->pcroffset), GST_TIME_ARGS (gstpcrtime));
|
|
} else {
|
|
GST_WARNING ("backward timestamps at server but no timestamps");
|
|
send_diff = 0;
|
|
/* at least try to get a new timestamp.. */
|
|
pcr->base_time = GST_CLOCK_TIME_NONE;
|
|
}
|
|
} else
|
|
send_diff = gstpcrtime - pcr->base_pcrtime;
|
|
|
|
GST_DEBUG ("gstpcr %" GST_TIME_FORMAT ", buftime %" GST_TIME_FORMAT
|
|
", base %" GST_TIME_FORMAT ", send_diff %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (gstpcrtime), GST_TIME_ARGS (time),
|
|
GST_TIME_ARGS (pcr->base_pcrtime), GST_TIME_ARGS (send_diff));
|
|
|
|
/* keep track of the last extended pcrtime */
|
|
pcr->last_pcrtime = gstpcrtime;
|
|
|
|
/* we don't have an arrival timestamp so we can't do skew detection. we
|
|
* should still apply a timestamp based on RTP timestamp and base_time */
|
|
if (!GST_CLOCK_TIME_IS_VALID (time)
|
|
|| !GST_CLOCK_TIME_IS_VALID (pcr->base_time))
|
|
goto no_skew;
|
|
|
|
/* elapsed time at receiver, includes the jitter */
|
|
recv_diff = time - pcr->base_time;
|
|
|
|
/* Ignore packets received at 100% the same time (i.e. from the same input buffer) */
|
|
if (G_UNLIKELY (time == pcr->prev_in_time
|
|
&& GST_CLOCK_TIME_IS_VALID (pcr->prev_in_time)))
|
|
goto no_skew;
|
|
|
|
/* measure the diff */
|
|
delta = ((gint64) recv_diff) - ((gint64) send_diff);
|
|
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
/* measure the slope, this gives a rought estimate between the sender speed
|
|
* and the receiver speed. This should be approximately 8, higher values
|
|
* indicate a burst (especially when the connection starts) */
|
|
slope = recv_diff > 0 ? (send_diff * 8) / recv_diff : 8;
|
|
#endif
|
|
|
|
GST_DEBUG ("time %" GST_TIME_FORMAT ", base %" GST_TIME_FORMAT
|
|
", recv_diff %" GST_TIME_FORMAT ", slope %" G_GUINT64_FORMAT,
|
|
GST_TIME_ARGS (time), GST_TIME_ARGS (pcr->base_time),
|
|
GST_TIME_ARGS (recv_diff), slope);
|
|
|
|
/* if the difference between the sender timeline and the receiver timeline
|
|
* changed too quickly we have to resync because the server likely restarted
|
|
* its timestamps. */
|
|
if (ABS (delta - pcr->skew) > GST_SECOND) {
|
|
GST_WARNING ("delta - skew: %" GST_TIME_FORMAT " too big, reset skew",
|
|
GST_TIME_ARGS (delta - pcr->skew));
|
|
mpegts_packetizer_resync (pcr, time, gstpcrtime, TRUE);
|
|
send_diff = 0;
|
|
delta = 0;
|
|
}
|
|
|
|
pos = pcr->window_pos;
|
|
|
|
if (G_UNLIKELY (pcr->window_filling)) {
|
|
/* we are filling the window */
|
|
GST_DEBUG ("filling %d, delta %" G_GINT64_FORMAT, pos, delta);
|
|
pcr->window[pos++] = delta;
|
|
/* calc the min delta we observed */
|
|
if (G_UNLIKELY (pos == 1 || delta < pcr->window_min))
|
|
pcr->window_min = delta;
|
|
|
|
if (G_UNLIKELY (send_diff >= MAX_TIME || pos >= MAX_WINDOW)) {
|
|
pcr->window_size = pos;
|
|
|
|
/* window filled */
|
|
GST_DEBUG ("min %" G_GINT64_FORMAT, pcr->window_min);
|
|
|
|
/* the skew is now the min */
|
|
pcr->skew = pcr->window_min;
|
|
pcr->window_filling = FALSE;
|
|
} else {
|
|
gint perc_time, perc_window, perc;
|
|
|
|
/* figure out how much we filled the window, this depends on the amount of
|
|
* time we have or the max number of points we keep. */
|
|
perc_time = send_diff * 100 / MAX_TIME;
|
|
perc_window = pos * 100 / MAX_WINDOW;
|
|
perc = MAX (perc_time, perc_window);
|
|
|
|
/* make a parabolic function, the closer we get to the MAX, the more value
|
|
* we give to the scaling factor of the new value */
|
|
perc = perc * perc;
|
|
|
|
/* quickly go to the min value when we are filling up, slowly when we are
|
|
* just starting because we're not sure it's a good value yet. */
|
|
pcr->skew =
|
|
(perc * pcr->window_min + ((10000 - perc) * pcr->skew)) / 10000;
|
|
pcr->window_size = pos + 1;
|
|
}
|
|
} else {
|
|
/* pick old value and store new value. We keep the previous value in order
|
|
* to quickly check if the min of the window changed */
|
|
old = pcr->window[pos];
|
|
pcr->window[pos++] = delta;
|
|
|
|
if (G_UNLIKELY (delta <= pcr->window_min)) {
|
|
/* if the new value we inserted is smaller or equal to the current min,
|
|
* it becomes the new min */
|
|
pcr->window_min = delta;
|
|
} else if (G_UNLIKELY (old == pcr->window_min)) {
|
|
gint64 min = G_MAXINT64;
|
|
|
|
/* if we removed the old min, we have to find a new min */
|
|
for (i = 0; i < pcr->window_size; i++) {
|
|
/* we found another value equal to the old min, we can stop searching now */
|
|
if (pcr->window[i] == old) {
|
|
min = old;
|
|
break;
|
|
}
|
|
if (pcr->window[i] < min)
|
|
min = pcr->window[i];
|
|
}
|
|
pcr->window_min = min;
|
|
}
|
|
/* average the min values */
|
|
pcr->skew = (pcr->window_min + (124 * pcr->skew)) / 125;
|
|
GST_DEBUG ("delta %" G_GINT64_FORMAT ", new min: %" G_GINT64_FORMAT,
|
|
delta, pcr->window_min);
|
|
}
|
|
/* wrap around in the window */
|
|
if (G_UNLIKELY (pos >= pcr->window_size))
|
|
pos = 0;
|
|
|
|
pcr->window_pos = pos;
|
|
|
|
no_skew:
|
|
/* the output time is defined as the base timestamp plus the PCR time
|
|
* adjusted for the clock skew .*/
|
|
if (pcr->base_time != -1) {
|
|
out_time = pcr->base_time + send_diff;
|
|
/* skew can be negative and we don't want to make invalid timestamps */
|
|
if (pcr->skew < 0 && out_time < -pcr->skew) {
|
|
out_time = 0;
|
|
} else {
|
|
out_time += pcr->skew;
|
|
}
|
|
/* check if timestamps are not going backwards, we can only check this if we
|
|
* have a previous out time and a previous send_diff */
|
|
if (G_LIKELY (pcr->prev_out_time != -1 && pcr->prev_send_diff != -1)) {
|
|
/* now check for backwards timestamps */
|
|
if (G_UNLIKELY (
|
|
/* if the server timestamps went up and the out_time backwards */
|
|
(send_diff > pcr->prev_send_diff
|
|
&& out_time < pcr->prev_out_time) ||
|
|
/* if the server timestamps went backwards and the out_time forwards */
|
|
(send_diff < pcr->prev_send_diff
|
|
&& out_time > pcr->prev_out_time) ||
|
|
/* if the server timestamps did not change */
|
|
send_diff == pcr->prev_send_diff)) {
|
|
GST_DEBUG ("backwards timestamps, using previous time");
|
|
out_time = GSTTIME_TO_MPEGTIME (out_time);
|
|
}
|
|
}
|
|
} else {
|
|
/* We simply use the pcrtime without applying any skew compensation */
|
|
out_time = time;
|
|
}
|
|
|
|
pcr->prev_out_time = out_time;
|
|
pcr->prev_in_time = time;
|
|
pcr->prev_send_diff = send_diff;
|
|
|
|
GST_DEBUG ("skew %" G_GINT64_FORMAT ", out %" GST_TIME_FORMAT,
|
|
pcr->skew, GST_TIME_ARGS (out_time));
|
|
|
|
return out_time;
|
|
}
|
|
|
|
static void
|
|
_reevaluate_group_pcr_offset (MpegTSPCR * pcrtable, PCROffsetGroup * group)
|
|
{
|
|
PCROffsetGroup *prev = NULL;
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
PCROffsetGroup *first = pcrtable->groups->data;
|
|
#endif
|
|
PCROffsetCurrent *current = pcrtable->current;
|
|
GList *tmp;
|
|
|
|
/* Go over all ESTIMATED groups until the target group */
|
|
for (tmp = pcrtable->groups; tmp; tmp = tmp->next) {
|
|
PCROffsetGroup *cur = (PCROffsetGroup *) tmp->data;
|
|
|
|
/* Skip groups that don't need re-evaluation */
|
|
if (!(cur->flags & PCR_GROUP_FLAG_ESTIMATED)) {
|
|
GST_DEBUG ("Skipping group %p pcr_offset (currently %" GST_TIME_FORMAT
|
|
")", cur, GST_TIME_ARGS (PCRTIME_TO_GSTTIME (cur->pcr_offset)));
|
|
prev = cur;
|
|
continue;
|
|
}
|
|
|
|
/* This should not happen ! The first group is *always* correct (zero) */
|
|
if (G_UNLIKELY (prev == NULL)) {
|
|
GST_ERROR ("First PCR Group was not estimated (bug). Setting to zero");
|
|
cur->pcr_offset = 0;
|
|
cur->flags &= ~PCR_GROUP_FLAG_ESTIMATED;
|
|
return;
|
|
}
|
|
|
|
/* Finally do the estimation of this group's PCR offset based on the
|
|
* previous group information */
|
|
|
|
GST_DEBUG ("Re-evaluating group %p pcr_offset (currently %" GST_TIME_FORMAT
|
|
")", group, GST_TIME_ARGS (PCRTIME_TO_GSTTIME (cur->pcr_offset)));
|
|
|
|
GST_DEBUG ("cur->first_pcr:%" GST_TIME_FORMAT " prev->first_pcr:%"
|
|
GST_TIME_FORMAT, GST_TIME_ARGS (PCRTIME_TO_GSTTIME (cur->first_pcr)),
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (prev->first_pcr)));
|
|
|
|
if (G_UNLIKELY (cur->first_pcr < prev->first_pcr)) {
|
|
guint64 prevbr, lastbr;
|
|
guint64 prevpcr;
|
|
guint64 prevoffset, lastoffset;
|
|
guint64 guess_offset;
|
|
|
|
/* Take the previous group pcr_offset and figure out how much to add
|
|
* to it for the current group */
|
|
guess_offset = prev->pcr_offset;
|
|
/* Right now we do a dumb bitrate estimation
|
|
* estimate bitrate (prev - first) : bitrate from the start
|
|
* estimate bitrate (prev) : bitrate of previous group
|
|
* estimate bitrate (last - first) : bitrate from previous group
|
|
*
|
|
* We will use raw (non-corrected/non-absolute) PCR values in a first time
|
|
* to detect wraparound/resets/gaps...
|
|
*
|
|
* We will use the corrected/asolute PCR values to calculate
|
|
* bitrate and estimate the target group pcr_offset.
|
|
* */
|
|
|
|
/* If the current window estimator is over the previous group, used those
|
|
* values as the latest (since they are more recent) */
|
|
if (current->group == prev && current->pending[current->last].offset) {
|
|
prevoffset =
|
|
current->pending[current->last].offset + prev->first_offset;
|
|
prevpcr = current->pending[current->last].pcr + prev->first_pcr;
|
|
/* prevbr: bitrate(prev) */
|
|
prevbr =
|
|
gst_util_uint64_scale (PCR_SECOND,
|
|
current->pending[current->last].offset,
|
|
current->pending[current->last].pcr);
|
|
GST_DEBUG ("Previous group bitrate (%u / %" GST_TIME_FORMAT ") : %"
|
|
G_GUINT64_FORMAT, current->pending[current->last].offset,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (current->pending[current->
|
|
last].pcr)), prevbr);
|
|
} else if (prev->values[prev->last_value].offset) {
|
|
prevoffset = prev->values[prev->last_value].offset + prev->first_offset;
|
|
prevpcr = prev->values[prev->last_value].pcr + prev->first_pcr;
|
|
/* prevbr: bitrate(prev) (FIXME : Cache) */
|
|
prevbr =
|
|
gst_util_uint64_scale (PCR_SECOND,
|
|
prev->values[prev->last_value].offset,
|
|
prev->values[prev->last_value].pcr);
|
|
GST_DEBUG ("Previous group bitrate (%u / %" GST_TIME_FORMAT ") : %"
|
|
G_GUINT64_FORMAT, prev->values[prev->last_value].offset,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (prev->values[prev->
|
|
last_value].pcr)), prevbr);
|
|
} else {
|
|
GST_DEBUG ("Using overall bitrate");
|
|
prevoffset = prev->values[prev->last_value].offset + prev->first_offset;
|
|
prevpcr = prev->values[prev->last_value].pcr + prev->first_pcr;
|
|
prevbr = gst_util_uint64_scale (PCR_SECOND,
|
|
prev->first_offset, prev->pcr_offset);
|
|
}
|
|
lastoffset = cur->values[cur->last_value].offset + cur->first_offset;
|
|
|
|
GST_DEBUG ("Offset first:%" G_GUINT64_FORMAT " prev:%" G_GUINT64_FORMAT
|
|
" cur:%" G_GUINT64_FORMAT, first->first_offset, prevoffset,
|
|
lastoffset);
|
|
GST_DEBUG ("PCR first:%" GST_TIME_FORMAT " prev:%" GST_TIME_FORMAT
|
|
" cur:%" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (first->first_pcr)),
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (prevpcr)),
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (cur->values[cur->last_value].pcr +
|
|
cur->first_pcr)));
|
|
|
|
if (prevpcr - cur->first_pcr > (PCR_MAX_VALUE * 9 / 10)) {
|
|
gfloat diffprev;
|
|
/* Let's assume there is a PCR wraparound between the previous and current
|
|
* group.
|
|
* [ prev ]... PCR_MAX | 0 ...[ current ]
|
|
* The estimated pcr_offset would therefore be:
|
|
* current.first + (PCR_MAX_VALUE - prev.first)
|
|
*
|
|
* 1) Check if bitrate(prev) would be consistent with bitrate (cur - prev)
|
|
*/
|
|
guess_offset = PCR_MAX_VALUE - prev->first_pcr + cur->first_pcr;
|
|
lastbr = gst_util_uint64_scale (PCR_SECOND, lastoffset - prevoffset,
|
|
guess_offset + cur->values[cur->last_value].pcr - (prevpcr -
|
|
prev->first_pcr));
|
|
GST_DEBUG ("Wraparound prev-cur (guess_offset:%" GST_TIME_FORMAT
|
|
") bitrate:%" G_GUINT64_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (guess_offset)), lastbr);
|
|
diffprev = (float) 100.0 *(ABSDIFF (prevbr, lastbr)) / (float) prevbr;
|
|
GST_DEBUG ("Difference with previous bitrate:%f", diffprev);
|
|
if (diffprev < 10.0) {
|
|
GST_DEBUG ("Difference < 10.0, Setting pcr_offset to %"
|
|
G_GUINT64_FORMAT, guess_offset);
|
|
cur->pcr_offset = guess_offset;
|
|
if (diffprev < 1.0) {
|
|
GST_DEBUG ("Difference < 1.0, Removing ESTIMATED flags");
|
|
cur->flags &= ~PCR_GROUP_FLAG_ESTIMATED;
|
|
}
|
|
}
|
|
/* Indicate the the previous group is before a wrapover */
|
|
prev->flags |= PCR_GROUP_FLAG_WRAPOVER;
|
|
} else {
|
|
guint64 resetprev;
|
|
/* Let's assume there was a PCR reset between the previous and current
|
|
* group
|
|
* [ prev ] ... x | x - reset ... [ current ]
|
|
*
|
|
* The estimated pcr_offset would then be
|
|
* = current.first - (x - reset) + (x - prev.first) + 100ms (for safety)
|
|
* = current.first + reset - prev.first + 100ms (for safety)
|
|
*/
|
|
/* In order to calculate the reset, we estimate what the PCR would have
|
|
* been by using prevbr */
|
|
/* FIXME : Which bitrate should we use ??? */
|
|
GST_DEBUG ("Using prevbr:%" G_GUINT64_FORMAT " and taking offsetdiff:%"
|
|
G_GUINT64_FORMAT, prevbr, cur->first_offset - prev->first_offset);
|
|
resetprev =
|
|
gst_util_uint64_scale (PCR_SECOND,
|
|
cur->first_offset - prev->first_offset, prevbr);
|
|
GST_DEBUG ("Estimated full PCR for offset %" G_GUINT64_FORMAT
|
|
", using prevbr:%"
|
|
GST_TIME_FORMAT, cur->first_offset,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (resetprev)));
|
|
cur->pcr_offset = prev->pcr_offset + resetprev + 100 * PCR_MSECOND;
|
|
GST_DEBUG ("Adjusted group PCR_offset to %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (cur->pcr_offset)));
|
|
/* Indicate the the previous group is before a reset */
|
|
prev->flags |= PCR_GROUP_FLAG_RESET;
|
|
}
|
|
} else {
|
|
/* FIXME : Detect gaps if bitrate difference is really too big ? */
|
|
cur->pcr_offset = prev->pcr_offset + cur->first_pcr - prev->first_pcr;
|
|
GST_DEBUG ("Assuming there is no gap, setting pcr_offset to %"
|
|
GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (cur->pcr_offset)));
|
|
/* Remove the reset and wrapover flag (if it was previously there) */
|
|
prev->flags &= ~PCR_GROUP_FLAG_RESET;
|
|
prev->flags &= ~PCR_GROUP_FLAG_WRAPOVER;
|
|
}
|
|
|
|
|
|
/* Remember prev for the next group evaluation */
|
|
prev = cur;
|
|
}
|
|
}
|
|
|
|
static PCROffsetGroup *
|
|
_new_group (guint64 pcr, guint64 offset, guint64 pcr_offset, guint flags)
|
|
{
|
|
PCROffsetGroup *group = g_slice_new0 (PCROffsetGroup);
|
|
|
|
GST_DEBUG ("Input PCR %" GST_TIME_FORMAT " offset:%" G_GUINT64_FORMAT
|
|
" pcr_offset:%" G_GUINT64_FORMAT " flags:%d",
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (pcr)), offset, pcr_offset, flags);
|
|
|
|
group->flags = flags;
|
|
group->values = g_new0 (PCROffset, DEFAULT_ALLOCATED_OFFSET);
|
|
/* The first pcr/offset diff is always 0/0 */
|
|
group->values[0].pcr = group->values[0].offset = 0;
|
|
group->nb_allocated = DEFAULT_ALLOCATED_OFFSET;
|
|
|
|
/* Store the full values */
|
|
group->first_pcr = pcr;
|
|
group->first_offset = offset;
|
|
group->pcr_offset = pcr_offset;
|
|
|
|
GST_DEBUG ("Created group starting with pcr:%" GST_TIME_FORMAT " offset:%"
|
|
G_GUINT64_FORMAT " pcr_offset:%" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->first_pcr)),
|
|
group->first_offset,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->pcr_offset)));
|
|
|
|
return group;
|
|
}
|
|
|
|
static void
|
|
_insert_group_after (MpegTSPCR * pcrtable, PCROffsetGroup * group,
|
|
PCROffsetGroup * prev)
|
|
{
|
|
if (prev == NULL) {
|
|
/* First group */
|
|
pcrtable->groups = g_list_prepend (pcrtable->groups, group);
|
|
} else {
|
|
GList *tmp, *toinsert, *prevlist = NULL, *nextlist = NULL;
|
|
/* Insert before next and prev */
|
|
for (tmp = pcrtable->groups; tmp; tmp = tmp->next) {
|
|
if (tmp->data == prev) {
|
|
prevlist = tmp;
|
|
nextlist = tmp->next;
|
|
break;
|
|
}
|
|
}
|
|
toinsert = g_list_append (NULL, group);
|
|
toinsert->next = nextlist;
|
|
toinsert->prev = prevlist;
|
|
prevlist->next = toinsert;
|
|
if (nextlist)
|
|
nextlist->prev = toinsert;
|
|
}
|
|
}
|
|
|
|
static void
|
|
_use_group (MpegTSPCR * pcrtable, PCROffsetGroup * group)
|
|
{
|
|
PCROffsetCurrent *current = pcrtable->current;
|
|
|
|
memset (current, 0, sizeof (PCROffsetCurrent));
|
|
current->group = group;
|
|
current->pending[0] = group->values[group->last_value];
|
|
current->last_value = current->pending[0];
|
|
current->write = 1;
|
|
current->prev = group->values[group->last_value];
|
|
current->first_pcr = group->first_pcr;
|
|
current->first_offset = group->first_offset;
|
|
}
|
|
|
|
/* Create a new group with the specified values after prev
|
|
* Set current to that new group */
|
|
static void
|
|
_set_current_group (MpegTSPCR * pcrtable,
|
|
PCROffsetGroup * prev, guint64 pcr, guint64 offset, gboolean contiguous)
|
|
{
|
|
PCROffsetGroup *group;
|
|
guint flags = 0;
|
|
guint64 pcr_offset = 0;
|
|
|
|
/* Handle wraparound/gap (only if contiguous with previous group) */
|
|
if (contiguous) {
|
|
guint64 lastpcr = prev->first_pcr + prev->values[prev->last_value].pcr;
|
|
|
|
/* Set CLOSED flag on previous group and remember pcr_offset */
|
|
prev->flags |= PCR_GROUP_FLAG_CLOSED;
|
|
pcr_offset = prev->pcr_offset;
|
|
|
|
/* Wraparound ? */
|
|
if (lastpcr > pcr) {
|
|
/* In offset-mode, a PCR wraparound is only actually consistent if
|
|
* we have a very high confidence (99% right now, might need to change
|
|
* later) */
|
|
if (lastpcr - pcr > (PCR_MAX_VALUE * 99 / 100)) {
|
|
GST_WARNING ("WRAPAROUND detected. diff %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (lastpcr - pcr)));
|
|
/* The previous group closed at PCR_MAX_VALUE */
|
|
pcr_offset += PCR_MAX_VALUE - prev->first_pcr + pcr;
|
|
} else {
|
|
GST_WARNING ("RESET detected. diff %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (lastpcr - pcr)));
|
|
/* The previous group closed at the raw last_pcr diff (+100ms for safety) */
|
|
pcr_offset += prev->values[prev->last_value].pcr + 100 * PCR_MSECOND;
|
|
}
|
|
} else if (lastpcr < pcr - 500 * PCR_MSECOND) {
|
|
GST_WARNING ("GAP detected. diff %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (pcr - lastpcr)));
|
|
/* The previous group closed at the raw last_pcr diff (+500ms for safety) */
|
|
pcr_offset += prev->values[prev->last_value].pcr + 500 * PCR_MSECOND;
|
|
} else
|
|
/* Normal continuation (contiguous in time) */
|
|
pcr_offset += pcr - prev->first_pcr;
|
|
|
|
} else if (prev != NULL)
|
|
/* If we are not contiguous and it's not the first group, the pcr_offset
|
|
* will be estimated */
|
|
flags = PCR_GROUP_FLAG_ESTIMATED;
|
|
|
|
group = _new_group (pcr, offset, pcr_offset, flags);
|
|
_use_group (pcrtable, group);
|
|
_insert_group_after (pcrtable, group, prev);
|
|
if (!contiguous)
|
|
_reevaluate_group_pcr_offset (pcrtable, group);
|
|
}
|
|
|
|
static inline void
|
|
_append_group_values (PCROffsetGroup * group, PCROffset pcroffset)
|
|
{
|
|
group->last_value++;
|
|
/* Resize values if needed */
|
|
if (G_UNLIKELY (group->nb_allocated == group->last_value)) {
|
|
group->nb_allocated += DEFAULT_ALLOCATED_OFFSET;
|
|
group->values =
|
|
g_realloc (group->values, group->nb_allocated * sizeof (PCROffset));
|
|
}
|
|
group->values[group->last_value] = pcroffset;
|
|
|
|
GST_DEBUG ("First PCR:%" GST_TIME_FORMAT " offset:%" G_GUINT64_FORMAT
|
|
" PCR_offset:%" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->first_pcr)),
|
|
group->first_offset,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->pcr_offset)));
|
|
GST_DEBUG ("Last PCR: +%" GST_TIME_FORMAT " offset: +%u",
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (pcroffset.pcr)), pcroffset.offset);
|
|
}
|
|
|
|
/* Move last values from current (if any) to the current group
|
|
* and reset current.
|
|
* Note: This does not set the CLOSED flag (since we have no next
|
|
* contiguous group) */
|
|
static void
|
|
_close_current_group (MpegTSPCR * pcrtable)
|
|
{
|
|
PCROffsetCurrent *current = pcrtable->current;
|
|
PCROffsetGroup *group = current->group;
|
|
|
|
if (group == NULL)
|
|
return;
|
|
GST_DEBUG ("Closing group and resetting current");
|
|
|
|
/* Store last values */
|
|
_append_group_values (group, current->pending[current->last]);
|
|
memset (current, 0, sizeof (PCROffsetCurrent));
|
|
/* And re-evaluate all groups */
|
|
}
|
|
|
|
static void
|
|
record_pcr (MpegTSPacketizer2 * packetizer, MpegTSPCR * pcrtable,
|
|
guint64 pcr, guint64 offset)
|
|
{
|
|
PCROffsetCurrent *current = pcrtable->current;
|
|
guint32 corpcr, coroffset;
|
|
|
|
packetizer->nb_seen_offsets += 1;
|
|
|
|
/* FIXME : Invert logic later (probability is higher that we have a
|
|
* current estimator) */
|
|
|
|
/* Check for current */
|
|
if (G_UNLIKELY (current->group == NULL)) {
|
|
PCROffsetGroup *prev = NULL;
|
|
GList *tmp;
|
|
/* No current estimator. This happens for the initial value, or after
|
|
* discont and flushes. Figure out where we need to record this position.
|
|
*
|
|
* Possible choices:
|
|
* 1) No groups at all:
|
|
* Create a new group with pcr/offset
|
|
* Initialize current to that group
|
|
* 2) Entirely within an existing group
|
|
* bail out (FIXME: Make this detection faster)
|
|
* 3) Not in any group
|
|
* Create a new group with pcr/offset at the right position
|
|
* Initialize current to that group
|
|
*/
|
|
GST_DEBUG ("No current window estimator, Checking for group to use");
|
|
for (tmp = pcrtable->groups; tmp; tmp = tmp->next) {
|
|
PCROffsetGroup *group = (PCROffsetGroup *) tmp->data;
|
|
|
|
GST_DEBUG ("First PCR:%" GST_TIME_FORMAT " offset:%" G_GUINT64_FORMAT
|
|
" PCR_offset:%" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->first_pcr)),
|
|
group->first_offset,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->pcr_offset)));
|
|
GST_DEBUG ("Last PCR: +%" GST_TIME_FORMAT " offset: +%u",
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->values[group->
|
|
last_value].pcr)),
|
|
group->values[group->last_value].offset);
|
|
/* Check if before group */
|
|
if (offset < group->first_offset) {
|
|
GST_DEBUG ("offset is before that group");
|
|
break;
|
|
}
|
|
/* Check if within group */
|
|
if (offset <=
|
|
(group->values[group->last_value].offset + group->first_offset)) {
|
|
GST_DEBUG ("Already observed PCR offset %" G_GUINT64_FORMAT, offset);
|
|
return;
|
|
}
|
|
/* Check if just after group (i.e. continuation of it) */
|
|
if (!(group->flags & PCR_GROUP_FLAG_CLOSED) &&
|
|
pcr - group->first_pcr - group->values[group->last_value].pcr <=
|
|
100 * PCR_MSECOND) {
|
|
GST_DEBUG ("Continuation of existing group");
|
|
_use_group (pcrtable, group);
|
|
return;
|
|
}
|
|
/* Else after group */
|
|
prev = group;
|
|
}
|
|
_set_current_group (pcrtable, prev, pcr, offset, FALSE);
|
|
return;
|
|
}
|
|
|
|
corpcr = pcr - current->first_pcr;
|
|
coroffset = offset - current->first_offset;
|
|
|
|
/* FIXME : Detect if we've gone into the next group !
|
|
* FIXME : Close group when that happens */
|
|
GST_DEBUG ("first:%d, last:%d, write:%d", current->first, current->last,
|
|
current->write);
|
|
GST_DEBUG ("First PCR:%" GST_TIME_FORMAT " offset:%" G_GUINT64_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (current->first_pcr)),
|
|
current->first_offset);
|
|
GST_DEBUG ("Last PCR: +%" GST_TIME_FORMAT " offset: +%u",
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (current->pending[current->last].pcr)),
|
|
current->pending[current->last].pcr);
|
|
GST_DEBUG ("To add (corrected) PCR:%" GST_TIME_FORMAT " offset:%u",
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (corpcr)), coroffset);
|
|
|
|
/* Do we need to close the current group ? */
|
|
/* Check for wrapover/discont */
|
|
if (G_UNLIKELY (corpcr < current->pending[current->last].pcr)) {
|
|
/* FIXME : ignore very small deltas (< 500ms ?) which are most likely
|
|
* stray values */
|
|
GST_DEBUG
|
|
("PCR smaller than previously observed one, handling discont/wrapover");
|
|
/* Take values from current and put them in the current group (closing it) */
|
|
/* Create new group with new pcr/offset just after the current group
|
|
* and mark it as a wrapover */
|
|
/* Initialize current to that group with new values */
|
|
_append_group_values (current->group, current->pending[current->last]);
|
|
_set_current_group (pcrtable, current->group, pcr, offset, TRUE);
|
|
return;
|
|
}
|
|
/* If PCR diff is greater than 500ms, create new group */
|
|
if (G_UNLIKELY (corpcr - current->pending[current->last].pcr >
|
|
500 * PCR_MSECOND)) {
|
|
GST_DEBUG ("New PCR more than 500ms away, handling discont");
|
|
/* Take values from current and put them in the current group (closing it) */
|
|
/* Create new group with pcr/offset just after the current group
|
|
* and mark it as a discont */
|
|
/* Initialize current to that group with new values */
|
|
_append_group_values (current->group, current->pending[current->last]);
|
|
_set_current_group (pcrtable, current->group, pcr, offset, TRUE);
|
|
return;
|
|
}
|
|
|
|
if (G_UNLIKELY (corpcr == current->last_value.pcr)) {
|
|
GST_DEBUG ("Ignoring same PCR (stream is drunk)");
|
|
return;
|
|
}
|
|
|
|
/* update current window */
|
|
current->pending[current->write].pcr = corpcr;
|
|
current->pending[current->write].offset = coroffset;
|
|
current->last_value = current->pending[current->write];
|
|
current->last = (current->last + 1) % PCR_BITRATE_NEEDED;
|
|
current->write = (current->write + 1) % PCR_BITRATE_NEEDED;
|
|
|
|
GST_DEBUG ("first:%d, last:%d, write:%d", current->first, current->last,
|
|
current->write);
|
|
GST_DEBUG ("First PCR:%" GST_TIME_FORMAT " offset:%" G_GUINT64_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (current->first_pcr)),
|
|
current->first_offset);
|
|
GST_DEBUG ("Last PCR: +%" GST_TIME_FORMAT " offset: +%u",
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (current->pending[current->last].pcr)),
|
|
current->pending[current->last].offset);
|
|
|
|
/* If we haven't stored enough values, bail out */
|
|
if (current->write != current->first) {
|
|
GST_DEBUG
|
|
("Not enough observations to calculate bitrate (first:%d, last:%d)",
|
|
current->first, current->last);
|
|
return;
|
|
}
|
|
|
|
/* If we are at least 1s away from reference value AND we have filled our
|
|
* window, we can start comparing bitrates */
|
|
if (current->pending[current->first].pcr - current->prev.pcr > PCR_SECOND) {
|
|
/* Calculate window bitrate */
|
|
current->cur_bitrate = gst_util_uint64_scale (PCR_SECOND,
|
|
current->pending[current->last].offset -
|
|
current->pending[current->first].offset,
|
|
current->pending[current->last].pcr -
|
|
current->pending[current->first].pcr);
|
|
GST_DEBUG ("Current bitrate is now %" G_GUINT64_FORMAT,
|
|
current->cur_bitrate);
|
|
|
|
/* Calculate previous bitrate */
|
|
current->prev_bitrate =
|
|
gst_util_uint64_scale (PCR_SECOND,
|
|
current->pending[current->first].offset - current->prev.offset,
|
|
current->pending[current->first].pcr - current->prev.pcr);
|
|
GST_DEBUG ("Previous group bitrate now %" G_GUINT64_FORMAT,
|
|
current->prev_bitrate);
|
|
|
|
/* FIXME : Better bitrate changes ? Currently 10% changes */
|
|
if (ABSDIFF (current->cur_bitrate,
|
|
current->prev_bitrate) * 10 > current->prev_bitrate) {
|
|
GST_DEBUG ("Current bitrate changed by more than 10%% (old:%"
|
|
G_GUINT64_FORMAT " new:%" G_GUINT64_FORMAT ")", current->prev_bitrate,
|
|
current->cur_bitrate);
|
|
/* If we detected a change in bitrate, this means that
|
|
* d(first - prev) is a different bitrate than d(last - first).
|
|
*
|
|
* Two conclusions can be made:
|
|
* 1) d(first - prev) is a complete bitrate "chain" (values between the
|
|
* reference value and first pending value have consistent bitrate).
|
|
* 2) next values (from second pending value onwards) will no longer have
|
|
* the same bitrate.
|
|
*
|
|
* The question remains as to how long the new bitrate change is going to
|
|
* last for (it might be short or longer term). For this we need to restart
|
|
* bitrate estimation.
|
|
*
|
|
* * We move over first to the last value of group (a new chain ends and
|
|
* starts from there)
|
|
* * We remember that last group value as our new window reference
|
|
* * We restart our window filing from the last observed value
|
|
*
|
|
* Once our new window is filled we will end up in two different scenarios:
|
|
* 1) Either the bitrate change was consistent, and therefore the bitrate
|
|
* will have remained constant over at least 2 window length
|
|
* 2) The bitrate change was very short (1 window duration) and we will
|
|
* close that chain and restart again.
|
|
* X) And of course if any discont/gaps/wrapover happen in the meantime they
|
|
* will also close the group.
|
|
*/
|
|
_append_group_values (current->group, current->pending[current->first]);
|
|
current->prev = current->pending[current->first];
|
|
current->first = current->last;
|
|
current->write = (current->first + 1) % PCR_BITRATE_NEEDED;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Update read position */
|
|
current->first = (current->first + 1) % PCR_BITRATE_NEEDED;
|
|
}
|
|
|
|
|
|
/* convert specified offset into stream time */
|
|
GstClockTime
|
|
mpegts_packetizer_offset_to_ts (MpegTSPacketizer2 * packetizer,
|
|
guint64 offset, guint16 pid)
|
|
{
|
|
PCROffsetGroup *last;
|
|
MpegTSPCR *pcrtable;
|
|
GList *tmp;
|
|
GstClockTime res;
|
|
guint64 lastpcr, lastoffset;
|
|
|
|
GST_DEBUG ("offset %" G_GUINT64_FORMAT, offset);
|
|
|
|
if (G_UNLIKELY (!packetizer->calculate_offset))
|
|
return GST_CLOCK_TIME_NONE;
|
|
|
|
if (G_UNLIKELY (packetizer->refoffset == -1))
|
|
return GST_CLOCK_TIME_NONE;
|
|
|
|
if (G_UNLIKELY (offset < packetizer->refoffset))
|
|
return GST_CLOCK_TIME_NONE;
|
|
|
|
pcrtable = get_pcr_table (packetizer, pid);
|
|
|
|
if (g_list_length (pcrtable->groups) < 2) {
|
|
GST_WARNING ("Not enough observations to return a duration estimate");
|
|
return GST_CLOCK_TIME_NONE;
|
|
}
|
|
|
|
/* FIXME : Refine this later to use neighbouring groups */
|
|
tmp = g_list_last (pcrtable->groups);
|
|
last = tmp->data;
|
|
|
|
if (G_UNLIKELY (last->flags & PCR_GROUP_FLAG_ESTIMATED))
|
|
_reevaluate_group_pcr_offset (pcrtable, last);
|
|
|
|
/* lastpcr is the full value in PCR from the first first chunk of data */
|
|
lastpcr = last->values[last->last_value].pcr + last->pcr_offset;
|
|
/* lastoffset is the full offset from the first chunk of data */
|
|
lastoffset =
|
|
last->values[last->last_value].offset + last->first_offset -
|
|
packetizer->refoffset;
|
|
|
|
GST_DEBUG ("lastpcr:%" GST_TIME_FORMAT " lastoffset:%" G_GUINT64_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (lastpcr)), lastoffset);
|
|
|
|
/* Convert byte difference into time difference (and transformed from 27MHz to 1GHz) */
|
|
res =
|
|
PCRTIME_TO_GSTTIME (gst_util_uint64_scale (offset - packetizer->refoffset,
|
|
lastpcr, lastoffset));
|
|
GST_DEBUG ("Returning timestamp %" GST_TIME_FORMAT " for offset %"
|
|
G_GUINT64_FORMAT, GST_TIME_ARGS (res), offset);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Input : local PTS (in GHz units)
|
|
* Return : Stream time (in GHz units) */
|
|
GstClockTime
|
|
mpegts_packetizer_pts_to_ts (MpegTSPacketizer2 * packetizer,
|
|
GstClockTime pts, guint16 pcr_pid)
|
|
{
|
|
GstClockTime res = GST_CLOCK_TIME_NONE;
|
|
MpegTSPCR *pcrtable = get_pcr_table (packetizer, pcr_pid);
|
|
|
|
/* Use clock skew if present */
|
|
if (packetizer->calculate_skew
|
|
&& GST_CLOCK_TIME_IS_VALID (pcrtable->base_time)) {
|
|
GST_DEBUG ("pts %" G_GUINT64_FORMAT " base_pcrtime:%" G_GUINT64_FORMAT
|
|
" base_time:%" GST_TIME_FORMAT, pts, pcrtable->base_pcrtime,
|
|
GST_TIME_ARGS (pcrtable->base_time));
|
|
res =
|
|
pts + pcrtable->pcroffset - pcrtable->base_pcrtime +
|
|
pcrtable->base_time + pcrtable->skew;
|
|
} else if (packetizer->calculate_offset && pcrtable->groups) {
|
|
gint64 refpcr = G_MAXINT64, refpcroffset;
|
|
PCROffsetGroup *group = pcrtable->current->group;
|
|
|
|
/* Generic calculation:
|
|
* Stream Time = PTS - first group PCR + group PCR_offset
|
|
*
|
|
* In case of wrapover:
|
|
* Stream Time = PTS + MAX_PCR - first group PCR + group PCR_offset
|
|
* (which we actually do by using first group PCR -= MAX_PCR in order
|
|
* to end up with the same calculation as for non-wrapover) */
|
|
|
|
if (group) {
|
|
/* If we have a current group the value is pretty much guaranteed */
|
|
GST_DEBUG ("Using current First PCR:%" GST_TIME_FORMAT " offset:%"
|
|
G_GUINT64_FORMAT " PCR_offset:%" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->first_pcr)),
|
|
group->first_offset,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->pcr_offset)));
|
|
refpcr = group->first_pcr;
|
|
refpcroffset = group->pcr_offset;
|
|
if (pts < PCRTIME_TO_GSTTIME (refpcr))
|
|
refpcr -= PCR_MAX_VALUE;
|
|
} else {
|
|
GList *tmp;
|
|
/* Otherwise, find a suitable group */
|
|
|
|
GST_DEBUG ("Find group for current offset %" G_GUINT64_FORMAT,
|
|
packetizer->offset);
|
|
|
|
for (tmp = pcrtable->groups; tmp; tmp = tmp->next) {
|
|
PCROffsetGroup *tgroup = tmp->data;
|
|
GST_DEBUG ("Trying First PCR:%" GST_TIME_FORMAT " offset:%"
|
|
G_GUINT64_FORMAT " PCR_offset:%" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (tgroup->first_pcr)),
|
|
tgroup->first_offset,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (tgroup->pcr_offset)));
|
|
/* Gone too far ? */
|
|
if (tgroup->first_offset > packetizer->offset) {
|
|
/* If there isn't a pending reset, use that value */
|
|
if (group) {
|
|
GST_DEBUG ("PTS is %" GST_TIME_FORMAT " into group",
|
|
GST_TIME_ARGS (pts - PCRTIME_TO_GSTTIME (group->first_pcr)));
|
|
}
|
|
break;
|
|
}
|
|
group = tgroup;
|
|
/* In that group ? */
|
|
if (group->first_offset + group->values[group->last_value].offset >
|
|
packetizer->offset) {
|
|
GST_DEBUG ("PTS is %" GST_TIME_FORMAT " into group",
|
|
GST_TIME_ARGS (pts - PCRTIME_TO_GSTTIME (group->first_pcr)));
|
|
break;
|
|
}
|
|
}
|
|
if (group && !(group->flags & PCR_GROUP_FLAG_RESET)) {
|
|
GST_DEBUG ("Using group !");
|
|
refpcr = group->first_pcr;
|
|
refpcroffset = group->pcr_offset;
|
|
if (pts < refpcr)
|
|
refpcr -= PCR_MAX_VALUE;
|
|
}
|
|
}
|
|
if (refpcr != G_MAXINT64)
|
|
res = pts - PCRTIME_TO_GSTTIME (refpcr - refpcroffset);
|
|
else
|
|
GST_WARNING ("No groups, can't calculate timestamp");
|
|
} else
|
|
GST_WARNING ("Not enough information to calculate proper timestamp");
|
|
|
|
GST_DEBUG ("Returning timestamp %" GST_TIME_FORMAT " for pts %"
|
|
GST_TIME_FORMAT " pcr_pid:0x%04x", GST_TIME_ARGS (res),
|
|
GST_TIME_ARGS (pts), pcr_pid);
|
|
return res;
|
|
}
|
|
|
|
/* Stream time to offset */
|
|
guint64
|
|
mpegts_packetizer_ts_to_offset (MpegTSPacketizer2 * packetizer,
|
|
GstClockTime ts, guint16 pcr_pid)
|
|
{
|
|
MpegTSPCR *pcrtable;
|
|
guint64 res;
|
|
PCROffsetGroup *nextgroup = NULL, *prevgroup = NULL;
|
|
guint64 querypcr, firstpcr, lastpcr, firstoffset, lastoffset;
|
|
GList *tmp;
|
|
|
|
if (!packetizer->calculate_offset)
|
|
return -1;
|
|
|
|
pcrtable = get_pcr_table (packetizer, pcr_pid);
|
|
|
|
if (pcrtable->groups == NULL)
|
|
return -1;
|
|
|
|
firstpcr = ((PCROffsetGroup *) pcrtable->groups->data)->first_pcr;
|
|
|
|
querypcr = GSTTIME_TO_PCRTIME (ts);
|
|
|
|
GST_DEBUG ("Searching offset for ts %" GST_TIME_FORMAT, GST_TIME_ARGS (ts));
|
|
|
|
/* Find the neighbouring groups */
|
|
for (tmp = pcrtable->groups; tmp; tmp = tmp->next) {
|
|
nextgroup = (PCROffsetGroup *) tmp->data;
|
|
|
|
GST_DEBUG ("Trying group PCR %" GST_TIME_FORMAT " (offset %"
|
|
G_GUINT64_FORMAT " pcr_offset %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (nextgroup->first_pcr)),
|
|
nextgroup->first_offset,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (nextgroup->pcr_offset)));
|
|
|
|
/* Check if we've gone too far */
|
|
if (nextgroup->pcr_offset > querypcr) {
|
|
GST_DEBUG ("pcr is before that group");
|
|
break;
|
|
}
|
|
|
|
prevgroup = nextgroup;
|
|
|
|
/* Maybe it's in this group */
|
|
if (nextgroup->values[nextgroup->last_value].pcr +
|
|
nextgroup->pcr_offset >= querypcr) {
|
|
GST_DEBUG ("pcr is in that group");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nextgroup == prevgroup) {
|
|
GST_DEBUG ("In group");
|
|
firstoffset = prevgroup->first_offset;
|
|
firstpcr = prevgroup->pcr_offset;
|
|
lastoffset =
|
|
prevgroup->values[prevgroup->last_value].offset +
|
|
prevgroup->first_offset;
|
|
lastpcr =
|
|
prevgroup->values[prevgroup->last_value].pcr + prevgroup->pcr_offset;
|
|
} else if (prevgroup) {
|
|
GST_DEBUG ("Between group");
|
|
lastoffset = nextgroup->first_offset;
|
|
lastpcr = nextgroup->pcr_offset;
|
|
firstoffset =
|
|
prevgroup->values[prevgroup->last_value].offset +
|
|
prevgroup->first_offset;
|
|
firstpcr =
|
|
prevgroup->values[prevgroup->last_value].pcr + prevgroup->pcr_offset;
|
|
} else {
|
|
GST_WARNING ("Not enough information to calculate offset");
|
|
return -1;
|
|
}
|
|
|
|
GST_DEBUG ("Using prev PCR %" G_GUINT64_FORMAT " offset %" G_GUINT64_FORMAT,
|
|
firstpcr, firstoffset);
|
|
GST_DEBUG ("Using last PCR %" G_GUINT64_FORMAT " offset %" G_GUINT64_FORMAT,
|
|
lastpcr, lastoffset);
|
|
|
|
res = firstoffset + gst_util_uint64_scale (querypcr - firstpcr,
|
|
lastoffset - firstoffset, lastpcr - firstpcr);
|
|
|
|
GST_DEBUG ("Returning offset %" G_GUINT64_FORMAT " for ts %"
|
|
GST_TIME_FORMAT, res, GST_TIME_ARGS (ts));
|
|
|
|
return res;
|
|
}
|
|
|
|
void
|
|
mpegts_packetizer_set_reference_offset (MpegTSPacketizer2 * packetizer,
|
|
guint64 refoffset)
|
|
{
|
|
GST_DEBUG ("Setting reference offset to %" G_GUINT64_FORMAT, refoffset);
|
|
|
|
packetizer->refoffset = refoffset;
|
|
}
|
|
|
|
void
|
|
mpegts_packetizer_set_current_pcr_offset (MpegTSPacketizer2 * packetizer,
|
|
GstClockTime offset, guint16 pcr_pid)
|
|
{
|
|
guint64 pcr_offset;
|
|
gint64 delta;
|
|
MpegTSPCR *pcrtable;
|
|
PCROffsetGroup *group;
|
|
GList *tmp;
|
|
gboolean apply = FALSE;
|
|
|
|
/* fast path */
|
|
pcrtable = get_pcr_table (packetizer, pcr_pid);
|
|
|
|
if (pcrtable == NULL || pcrtable->current->group == NULL)
|
|
return;
|
|
|
|
pcr_offset = GSTTIME_TO_PCRTIME (offset);
|
|
|
|
group = pcrtable->current->group;
|
|
GST_DEBUG ("Current group PCR %" GST_TIME_FORMAT " (offset %"
|
|
G_GUINT64_FORMAT " pcr_offset %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->first_pcr)),
|
|
group->first_offset,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->pcr_offset)));
|
|
|
|
/* Remember the difference between previous initial pcr_offset and
|
|
* new initial pcr_offset */
|
|
delta = pcr_offset - group->pcr_offset;
|
|
GST_DEBUG ("Shifting groups by %" GST_TIME_FORMAT
|
|
" for new initial pcr_offset %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (delta)), GST_TIME_ARGS (offset));
|
|
|
|
for (tmp = pcrtable->groups; tmp; tmp = tmp->next) {
|
|
PCROffsetGroup *tgroup = (tmp->data);
|
|
if (tgroup == group)
|
|
apply = TRUE;
|
|
if (apply) {
|
|
group->pcr_offset += delta;
|
|
GST_DEBUG ("Update group PCR %" GST_TIME_FORMAT " (offset %"
|
|
G_GUINT64_FORMAT " pcr_offset %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->first_pcr)),
|
|
group->first_offset,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->pcr_offset)));
|
|
} else
|
|
GST_DEBUG ("Not modifying group PCR %" GST_TIME_FORMAT " (offset %"
|
|
G_GUINT64_FORMAT " pcr_offset %" GST_TIME_FORMAT,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->first_pcr)),
|
|
group->first_offset,
|
|
GST_TIME_ARGS (PCRTIME_TO_GSTTIME (group->pcr_offset)));
|
|
}
|
|
}
|