mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-20 23:36:38 +00:00
1212 lines
32 KiB
C
1212 lines
32 KiB
C
/*
|
|
* gstmpegtssection.c -
|
|
* Copyright (C) 2013 Edward Hervey
|
|
* Copyright (C) 2011, Hewlett-Packard Development Company, L.P.
|
|
* Copyright (C) 2007 Alessandro Decina
|
|
* 2010 Edward Hervey
|
|
* Author: Youness Alaoui <youness.alaoui@collabora.co.uk>, Collabora Ltd.
|
|
* Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>, Collabora Ltd.
|
|
* Author: Edward Hervey <bilboed@bilboed.com>, Collabora Ltd.
|
|
*
|
|
* Authors:
|
|
* Alessandro Decina <alessandro@nnva.org>
|
|
* Zaheer Abbas Merali <zaheerabbas at merali dot org>
|
|
* Edward Hervey <edward@collabora.com>
|
|
*
|
|
* 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.
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "mpegts.h"
|
|
#include "gstmpegts-private.h"
|
|
|
|
/**
|
|
* SECTION:gst-dvb-section
|
|
* @title: DVB variants of MPEG-TS sections
|
|
* @short_description: Sections for the various DVB specifications
|
|
* @include: gst/mpegts/mpegts.h
|
|
*
|
|
*/
|
|
|
|
|
|
/*
|
|
* TODO
|
|
*
|
|
* * Check minimum size for section parsing in the various
|
|
* gst_mpegts_section_get_<tabld>() methods
|
|
*
|
|
* * Implement parsing code for
|
|
* * BAT
|
|
* * CAT
|
|
* * TSDT
|
|
*/
|
|
|
|
static inline GstDateTime *
|
|
_parse_utc_time (guint8 * data)
|
|
{
|
|
guint year, month, day, hour, minute, second;
|
|
guint16 mjd;
|
|
guint8 *utc_ptr;
|
|
|
|
mjd = GST_READ_UINT16_BE (data);
|
|
|
|
if (mjd == G_MAXUINT16)
|
|
return NULL;
|
|
|
|
/* See EN 300 468 Annex C */
|
|
year = (guint32) (((mjd - 15078.2) / 365.25));
|
|
month = (guint8) ((mjd - 14956.1 - (guint) (year * 365.25)) / 30.6001);
|
|
day = mjd - 14956 - (guint) (year * 365.25) - (guint) (month * 30.6001);
|
|
if (month == 14 || month == 15) {
|
|
year++;
|
|
month = month - 1 - 12;
|
|
} else {
|
|
month--;
|
|
}
|
|
year += 1900;
|
|
|
|
utc_ptr = data + 2;
|
|
|
|
/* First digit of hours cannot exceed 1 (max: 23 hours) */
|
|
hour = ((utc_ptr[0] & 0x30) >> 4) * 10 + (utc_ptr[0] & 0x0F);
|
|
/* First digit of minutes cannot exced 5 (max: 59 mins) */
|
|
minute = ((utc_ptr[1] & 0x70) >> 4) * 10 + (utc_ptr[1] & 0x0F);
|
|
/* first digit of seconds cannot exceed 5 (max: 59 seconds) */
|
|
second = ((utc_ptr[2] & 0x70) >> 4) * 10 + (utc_ptr[2] & 0x0F);
|
|
|
|
/* Time is UTC */
|
|
if (hour < 24 && minute < 60 && second < 60) {
|
|
return gst_date_time_new (0.0, year, month, day, hour, minute,
|
|
(gdouble) second);
|
|
} else if (utc_ptr[0] == 0xFF && utc_ptr[1] == 0xFF && utc_ptr[2] == 0xFF) {
|
|
return gst_date_time_new (0.0, year, month, day, -1, -1, -1);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Event Information Table */
|
|
static GstMpegtsEITEvent *
|
|
_gst_mpegts_eit_event_copy (GstMpegtsEITEvent * eit)
|
|
{
|
|
GstMpegtsEITEvent *copy;
|
|
|
|
copy = g_slice_dup (GstMpegtsEITEvent, eit);
|
|
copy->start_time = gst_date_time_ref (eit->start_time);
|
|
copy->descriptors = g_ptr_array_ref (eit->descriptors);
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
_gst_mpegts_eit_event_free (GstMpegtsEITEvent * eit)
|
|
{
|
|
if (eit->start_time)
|
|
gst_date_time_unref (eit->start_time);
|
|
if (eit->descriptors)
|
|
g_ptr_array_unref (eit->descriptors);
|
|
g_slice_free (GstMpegtsEITEvent, eit);
|
|
}
|
|
|
|
G_DEFINE_BOXED_TYPE (GstMpegtsEITEvent, gst_mpegts_eit_event,
|
|
(GBoxedCopyFunc) _gst_mpegts_eit_event_copy,
|
|
(GFreeFunc) _gst_mpegts_eit_event_free);
|
|
|
|
static GstMpegtsEIT *
|
|
_gst_mpegts_eit_copy (GstMpegtsEIT * eit)
|
|
{
|
|
GstMpegtsEIT *copy;
|
|
|
|
copy = g_slice_dup (GstMpegtsEIT, eit);
|
|
copy->events = g_ptr_array_ref (eit->events);
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
_gst_mpegts_eit_free (GstMpegtsEIT * eit)
|
|
{
|
|
g_ptr_array_unref (eit->events);
|
|
g_slice_free (GstMpegtsEIT, eit);
|
|
}
|
|
|
|
G_DEFINE_BOXED_TYPE (GstMpegtsEIT, gst_mpegts_eit,
|
|
(GBoxedCopyFunc) _gst_mpegts_eit_copy, (GFreeFunc) _gst_mpegts_eit_free);
|
|
|
|
static gpointer
|
|
_parse_eit (GstMpegtsSection * section)
|
|
{
|
|
GstMpegtsEIT *eit = NULL;
|
|
guint i = 0, allocated_events = 12;
|
|
guint8 *data, *end, *duration_ptr;
|
|
guint16 descriptors_loop_length;
|
|
|
|
eit = g_slice_new0 (GstMpegtsEIT);
|
|
|
|
data = section->data;
|
|
end = data + section->section_length;
|
|
|
|
/* Skip already parsed data */
|
|
data += 8;
|
|
|
|
eit->transport_stream_id = GST_READ_UINT16_BE (data);
|
|
data += 2;
|
|
eit->original_network_id = GST_READ_UINT16_BE (data);
|
|
data += 2;
|
|
eit->segment_last_section_number = *data++;
|
|
eit->last_table_id = *data++;
|
|
|
|
eit->actual_stream = (section->table_id == 0x4E ||
|
|
(section->table_id >= 0x50 && section->table_id <= 0x5F));
|
|
eit->present_following = (section->table_id == 0x4E
|
|
|| section->table_id == 0x4F);
|
|
|
|
eit->events =
|
|
g_ptr_array_new_full (allocated_events,
|
|
(GDestroyNotify) _gst_mpegts_eit_event_free);
|
|
|
|
while (data < end - 4) {
|
|
GstMpegtsEITEvent *event;
|
|
|
|
/* 12 is the minimum entry size + CRC */
|
|
if (end - data < 12 + 4) {
|
|
GST_WARNING ("PID %d invalid EIT entry length %d",
|
|
section->pid, (gint) (end - 4 - data));
|
|
goto error;
|
|
}
|
|
|
|
event = g_slice_new0 (GstMpegtsEITEvent);
|
|
g_ptr_array_add (eit->events, event);
|
|
|
|
event->event_id = GST_READ_UINT16_BE (data);
|
|
data += 2;
|
|
|
|
event->start_time = _parse_utc_time (data);
|
|
duration_ptr = data + 5;
|
|
event->duration = (((duration_ptr[0] & 0xF0) >> 4) * 10 +
|
|
(duration_ptr[0] & 0x0F)) * 60 * 60 +
|
|
(((duration_ptr[1] & 0xF0) >> 4) * 10 +
|
|
(duration_ptr[1] & 0x0F)) * 60 +
|
|
((duration_ptr[2] & 0xF0) >> 4) * 10 + (duration_ptr[2] & 0x0F);
|
|
|
|
data += 8;
|
|
event->running_status = *data >> 5;
|
|
event->free_CA_mode = (*data >> 4) & 0x01;
|
|
|
|
descriptors_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF;
|
|
data += 2;
|
|
|
|
event->descriptors =
|
|
gst_mpegts_parse_descriptors (data, descriptors_loop_length);
|
|
if (event->descriptors == NULL)
|
|
goto error;
|
|
data += descriptors_loop_length;
|
|
|
|
i += 1;
|
|
}
|
|
|
|
if (data != end - 4) {
|
|
GST_WARNING ("PID %d invalid EIT parsed %d length %d",
|
|
section->pid, (gint) (data - section->data), section->section_length);
|
|
goto error;
|
|
}
|
|
|
|
return (gpointer) eit;
|
|
|
|
error:
|
|
if (eit)
|
|
_gst_mpegts_eit_free (eit);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
/**
|
|
* gst_mpegts_section_get_eit:
|
|
* @section: a #GstMpegtsSection of type %GST_MPEGTS_SECTION_EIT
|
|
*
|
|
* Returns the #GstMpegtsEIT contained in the @section.
|
|
*
|
|
* Returns: The #GstMpegtsEIT contained in the section, or %NULL if an error
|
|
* happened.
|
|
*/
|
|
const GstMpegtsEIT *
|
|
gst_mpegts_section_get_eit (GstMpegtsSection * section)
|
|
{
|
|
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_EIT, NULL);
|
|
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
|
|
|
|
if (!section->cached_parsed)
|
|
section->cached_parsed = __common_section_checks (section, 18, _parse_eit,
|
|
(GDestroyNotify) _gst_mpegts_eit_free);
|
|
|
|
return (const GstMpegtsEIT *) section->cached_parsed;
|
|
}
|
|
|
|
/* Bouquet Association Table */
|
|
static GstMpegtsBATStream *
|
|
_gst_mpegts_bat_stream_copy (GstMpegtsBATStream * bat)
|
|
{
|
|
GstMpegtsBATStream *copy;
|
|
|
|
copy = g_slice_dup (GstMpegtsBATStream, bat);
|
|
copy->descriptors = g_ptr_array_ref (bat->descriptors);
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
_gst_mpegts_bat_stream_free (GstMpegtsBATStream * bat)
|
|
{
|
|
if (bat->descriptors)
|
|
g_ptr_array_unref (bat->descriptors);
|
|
g_slice_free (GstMpegtsBATStream, bat);
|
|
}
|
|
|
|
G_DEFINE_BOXED_TYPE (GstMpegtsBATStream, gst_mpegts_bat_stream,
|
|
(GBoxedCopyFunc) _gst_mpegts_bat_stream_copy,
|
|
(GFreeFunc) _gst_mpegts_bat_stream_free);
|
|
|
|
static GstMpegtsBAT *
|
|
_gst_mpegts_bat_copy (GstMpegtsBAT * bat)
|
|
{
|
|
GstMpegtsBAT *copy;
|
|
|
|
copy = g_slice_dup (GstMpegtsBAT, bat);
|
|
copy->descriptors = g_ptr_array_ref (bat->descriptors);
|
|
copy->streams = g_ptr_array_ref (bat->streams);
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
_gst_mpegts_bat_free (GstMpegtsBAT * bat)
|
|
{
|
|
if (bat->descriptors)
|
|
g_ptr_array_unref (bat->descriptors);
|
|
if (bat->streams)
|
|
g_ptr_array_unref (bat->streams);
|
|
g_slice_free (GstMpegtsBAT, bat);
|
|
}
|
|
|
|
G_DEFINE_BOXED_TYPE (GstMpegtsBAT, gst_mpegts_bat,
|
|
(GBoxedCopyFunc) _gst_mpegts_bat_copy, (GFreeFunc) _gst_mpegts_bat_free);
|
|
|
|
static gpointer
|
|
_parse_bat (GstMpegtsSection * section)
|
|
{
|
|
GstMpegtsBAT *bat = NULL;
|
|
guint i = 0, allocated_streams = 12;
|
|
guint8 *data, *end, *entry_begin;
|
|
guint16 descriptors_loop_length, transport_stream_loop_length;
|
|
|
|
GST_DEBUG ("BAT");
|
|
|
|
bat = g_slice_new0 (GstMpegtsBAT);
|
|
|
|
data = section->data;
|
|
end = data + section->section_length;
|
|
|
|
/* Skip already parsed data */
|
|
data += 8;
|
|
|
|
descriptors_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF;
|
|
data += 2;
|
|
|
|
/* see if the buffer is large enough */
|
|
if (descriptors_loop_length && (data + descriptors_loop_length > end - 4)) {
|
|
GST_WARNING ("PID %d invalid BAT descriptors loop length %d",
|
|
section->pid, descriptors_loop_length);
|
|
goto error;
|
|
}
|
|
bat->descriptors =
|
|
gst_mpegts_parse_descriptors (data, descriptors_loop_length);
|
|
if (bat->descriptors == NULL)
|
|
goto error;
|
|
data += descriptors_loop_length;
|
|
|
|
transport_stream_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF;
|
|
data += 2;
|
|
if (G_UNLIKELY (transport_stream_loop_length > (end - 4 - data))) {
|
|
GST_WARNING
|
|
("PID 0x%04x invalid BAT (transport_stream_loop_length too big)",
|
|
section->pid);
|
|
goto error;
|
|
}
|
|
|
|
bat->streams =
|
|
g_ptr_array_new_full (allocated_streams,
|
|
(GDestroyNotify) _gst_mpegts_bat_stream_free);
|
|
|
|
/* read up to the CRC */
|
|
while (transport_stream_loop_length - 4 > 0) {
|
|
GstMpegtsBATStream *stream = g_slice_new0 (GstMpegtsBATStream);
|
|
|
|
g_ptr_array_add (bat->streams, stream);
|
|
|
|
if (transport_stream_loop_length < 6) {
|
|
/* each entry must be at least 6 bytes (+ 4 bytes CRC) */
|
|
GST_WARNING ("PID %d invalid BAT entry size %d",
|
|
section->pid, transport_stream_loop_length);
|
|
goto error;
|
|
}
|
|
|
|
entry_begin = data;
|
|
|
|
stream->transport_stream_id = GST_READ_UINT16_BE (data);
|
|
data += 2;
|
|
|
|
stream->original_network_id = GST_READ_UINT16_BE (data);
|
|
data += 2;
|
|
|
|
descriptors_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF;
|
|
data += 2;
|
|
|
|
GST_DEBUG ("descriptors_loop_length %d", descriptors_loop_length);
|
|
|
|
if (descriptors_loop_length && (data + descriptors_loop_length > end - 4)) {
|
|
GST_WARNING
|
|
("PID %d invalid BAT entry %d descriptors loop length %d (only have %"
|
|
G_GSIZE_FORMAT ")", section->pid, section->subtable_extension,
|
|
descriptors_loop_length, (gsize) (end - 4 - data));
|
|
goto error;
|
|
}
|
|
stream->descriptors =
|
|
gst_mpegts_parse_descriptors (data, descriptors_loop_length);
|
|
if (stream->descriptors == NULL)
|
|
goto error;
|
|
|
|
data += descriptors_loop_length;
|
|
|
|
i += 1;
|
|
transport_stream_loop_length -= data - entry_begin;
|
|
}
|
|
|
|
if (data != end - 4) {
|
|
GST_WARNING ("PID %d invalid BAT parsed %d length %d",
|
|
section->pid, (gint) (data - section->data), section->section_length);
|
|
goto error;
|
|
}
|
|
|
|
return (gpointer) bat;
|
|
|
|
error:
|
|
if (bat)
|
|
_gst_mpegts_bat_free (bat);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* gst_mpegts_section_get_bat:
|
|
* @section: a #GstMpegtsSection of type %GST_MPEGTS_SECTION_BAT
|
|
*
|
|
* Returns the #GstMpegtsBAT contained in the @section.
|
|
*
|
|
* Returns: The #GstMpegtsBAT contained in the section, or %NULL if an error
|
|
* happened.
|
|
*/
|
|
const GstMpegtsBAT *
|
|
gst_mpegts_section_get_bat (GstMpegtsSection * section)
|
|
{
|
|
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_BAT, NULL);
|
|
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
|
|
|
|
if (!section->cached_parsed)
|
|
section->cached_parsed =
|
|
__common_section_checks (section, 16, _parse_bat,
|
|
(GDestroyNotify) _gst_mpegts_bat_free);
|
|
|
|
return (const GstMpegtsBAT *) section->cached_parsed;
|
|
}
|
|
|
|
|
|
/* Network Information Table */
|
|
|
|
static GstMpegtsNITStream *
|
|
_gst_mpegts_nit_stream_copy (GstMpegtsNITStream * nit)
|
|
{
|
|
GstMpegtsNITStream *copy;
|
|
|
|
copy = g_slice_dup (GstMpegtsNITStream, nit);
|
|
copy->descriptors = g_ptr_array_ref (nit->descriptors);
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
_gst_mpegts_nit_stream_free (GstMpegtsNITStream * nit)
|
|
{
|
|
if (nit->descriptors)
|
|
g_ptr_array_unref (nit->descriptors);
|
|
g_slice_free (GstMpegtsNITStream, nit);
|
|
}
|
|
|
|
G_DEFINE_BOXED_TYPE (GstMpegtsNITStream, gst_mpegts_nit_stream,
|
|
(GBoxedCopyFunc) _gst_mpegts_nit_stream_copy,
|
|
(GFreeFunc) _gst_mpegts_nit_stream_free);
|
|
|
|
static GstMpegtsNIT *
|
|
_gst_mpegts_nit_copy (GstMpegtsNIT * nit)
|
|
{
|
|
GstMpegtsNIT *copy = g_slice_dup (GstMpegtsNIT, nit);
|
|
|
|
copy->descriptors = g_ptr_array_ref (nit->descriptors);
|
|
copy->streams = g_ptr_array_ref (nit->streams);
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
_gst_mpegts_nit_free (GstMpegtsNIT * nit)
|
|
{
|
|
if (nit->descriptors)
|
|
g_ptr_array_unref (nit->descriptors);
|
|
g_ptr_array_unref (nit->streams);
|
|
g_slice_free (GstMpegtsNIT, nit);
|
|
}
|
|
|
|
G_DEFINE_BOXED_TYPE (GstMpegtsNIT, gst_mpegts_nit,
|
|
(GBoxedCopyFunc) _gst_mpegts_nit_copy, (GFreeFunc) _gst_mpegts_nit_free);
|
|
|
|
|
|
static gpointer
|
|
_parse_nit (GstMpegtsSection * section)
|
|
{
|
|
GstMpegtsNIT *nit = NULL;
|
|
guint i = 0, allocated_streams = 12;
|
|
guint8 *data, *end, *entry_begin;
|
|
guint16 descriptors_loop_length, transport_stream_loop_length;
|
|
|
|
GST_DEBUG ("NIT");
|
|
|
|
nit = g_slice_new0 (GstMpegtsNIT);
|
|
|
|
data = section->data;
|
|
end = data + section->section_length;
|
|
|
|
/* Set network id, and skip the rest of what is already parsed */
|
|
nit->network_id = section->subtable_extension;
|
|
data += 8;
|
|
|
|
nit->actual_network = section->table_id == 0x40;
|
|
|
|
descriptors_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF;
|
|
data += 2;
|
|
|
|
/* see if the buffer is large enough */
|
|
if (descriptors_loop_length && (data + descriptors_loop_length > end - 4)) {
|
|
GST_WARNING ("PID %d invalid NIT descriptors loop length %d",
|
|
section->pid, descriptors_loop_length);
|
|
goto error;
|
|
}
|
|
nit->descriptors =
|
|
gst_mpegts_parse_descriptors (data, descriptors_loop_length);
|
|
if (nit->descriptors == NULL)
|
|
goto error;
|
|
data += descriptors_loop_length;
|
|
|
|
transport_stream_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF;
|
|
data += 2;
|
|
if (G_UNLIKELY (transport_stream_loop_length > (end - 4 - data))) {
|
|
GST_WARNING
|
|
("PID 0x%04x invalid NIT (transport_stream_loop_length too big)",
|
|
section->pid);
|
|
goto error;
|
|
}
|
|
|
|
nit->streams =
|
|
g_ptr_array_new_full (allocated_streams,
|
|
(GDestroyNotify) _gst_mpegts_nit_stream_free);
|
|
|
|
/* read up to the CRC */
|
|
while (transport_stream_loop_length - 4 > 0) {
|
|
GstMpegtsNITStream *stream = g_slice_new0 (GstMpegtsNITStream);
|
|
|
|
g_ptr_array_add (nit->streams, stream);
|
|
|
|
if (transport_stream_loop_length < 6) {
|
|
/* each entry must be at least 6 bytes (+ 4 bytes CRC) */
|
|
GST_WARNING ("PID %d invalid NIT entry size %d",
|
|
section->pid, transport_stream_loop_length);
|
|
goto error;
|
|
}
|
|
|
|
entry_begin = data;
|
|
|
|
stream->transport_stream_id = GST_READ_UINT16_BE (data);
|
|
data += 2;
|
|
|
|
stream->original_network_id = GST_READ_UINT16_BE (data);
|
|
data += 2;
|
|
|
|
descriptors_loop_length = GST_READ_UINT16_BE (data) & 0x0FFF;
|
|
data += 2;
|
|
|
|
GST_DEBUG ("descriptors_loop_length %d", descriptors_loop_length);
|
|
|
|
if (descriptors_loop_length && (data + descriptors_loop_length > end - 4)) {
|
|
GST_WARNING
|
|
("PID %d invalid NIT entry %d descriptors loop length %d (only have %"
|
|
G_GSIZE_FORMAT ")", section->pid, section->subtable_extension,
|
|
descriptors_loop_length, (gsize) (end - 4 - data));
|
|
goto error;
|
|
}
|
|
stream->descriptors =
|
|
gst_mpegts_parse_descriptors (data, descriptors_loop_length);
|
|
if (stream->descriptors == NULL)
|
|
goto error;
|
|
|
|
data += descriptors_loop_length;
|
|
|
|
i += 1;
|
|
transport_stream_loop_length -= data - entry_begin;
|
|
}
|
|
|
|
if (data != end - 4) {
|
|
GST_WARNING ("PID %d invalid NIT parsed %d length %d",
|
|
section->pid, (gint) (data - section->data), section->section_length);
|
|
goto error;
|
|
}
|
|
|
|
return (gpointer) nit;
|
|
|
|
error:
|
|
if (nit)
|
|
_gst_mpegts_nit_free (nit);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* gst_mpegts_section_get_nit:
|
|
* @section: a #GstMpegtsSection of type %GST_MPEGTS_SECTION_NIT
|
|
*
|
|
* Returns the #GstMpegtsNIT contained in the @section.
|
|
*
|
|
* Returns: The #GstMpegtsNIT contained in the section, or %NULL if an error
|
|
* happened.
|
|
*/
|
|
const GstMpegtsNIT *
|
|
gst_mpegts_section_get_nit (GstMpegtsSection * section)
|
|
{
|
|
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_NIT, NULL);
|
|
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
|
|
|
|
if (!section->cached_parsed)
|
|
section->cached_parsed =
|
|
__common_section_checks (section, 16, _parse_nit,
|
|
(GDestroyNotify) _gst_mpegts_nit_free);
|
|
|
|
return (const GstMpegtsNIT *) section->cached_parsed;
|
|
}
|
|
|
|
/**
|
|
* gst_mpegts_nit_new:
|
|
*
|
|
* Allocates and initializes a #GstMpegtsNIT.
|
|
*
|
|
* Returns: (transfer full): A newly allocated #GstMpegtsNIT
|
|
*/
|
|
GstMpegtsNIT *
|
|
gst_mpegts_nit_new (void)
|
|
{
|
|
GstMpegtsNIT *nit;
|
|
|
|
nit = g_slice_new0 (GstMpegtsNIT);
|
|
|
|
nit->descriptors = g_ptr_array_new_with_free_func ((GDestroyNotify)
|
|
gst_mpegts_descriptor_free);
|
|
nit->streams = g_ptr_array_new_with_free_func ((GDestroyNotify)
|
|
_gst_mpegts_nit_stream_free);
|
|
|
|
return nit;
|
|
}
|
|
|
|
/**
|
|
* gst_mpegts_nit_stream_new:
|
|
*
|
|
* Allocates and initializes a #GstMpegtsNITStream
|
|
*
|
|
* Returns: (transfer full): A newly allocated #GstMpegtsNITStream
|
|
*/
|
|
GstMpegtsNITStream *
|
|
gst_mpegts_nit_stream_new (void)
|
|
{
|
|
GstMpegtsNITStream *stream;
|
|
|
|
stream = g_slice_new0 (GstMpegtsNITStream);
|
|
|
|
stream->descriptors = g_ptr_array_new_with_free_func (
|
|
(GDestroyNotify) gst_mpegts_descriptor_free);
|
|
|
|
return stream;
|
|
}
|
|
|
|
static gboolean
|
|
_packetize_nit (GstMpegtsSection * section)
|
|
{
|
|
gsize length, network_length, loop_length;
|
|
const GstMpegtsNIT *nit;
|
|
GstMpegtsNITStream *stream;
|
|
GstMpegtsDescriptor *descriptor;
|
|
guint i, j;
|
|
guint8 *data, *pos;
|
|
|
|
nit = gst_mpegts_section_get_nit (section);
|
|
|
|
if (nit == NULL)
|
|
return FALSE;
|
|
|
|
/* 8 byte common section fields
|
|
2 byte network_descriptors_length
|
|
2 byte transport_stream_loop_length
|
|
4 byte CRC */
|
|
length = 16;
|
|
|
|
/* Find length of network descriptors */
|
|
network_length = 0;
|
|
if (nit->descriptors) {
|
|
for (i = 0; i < nit->descriptors->len; i++) {
|
|
descriptor = g_ptr_array_index (nit->descriptors, i);
|
|
network_length += descriptor->length + 2;
|
|
}
|
|
}
|
|
|
|
/* Find length of loop */
|
|
loop_length = 0;
|
|
if (nit->streams) {
|
|
for (i = 0; i < nit->streams->len; i++) {
|
|
stream = g_ptr_array_index (nit->streams, i);
|
|
loop_length += 6;
|
|
if (stream->descriptors) {
|
|
for (j = 0; j < stream->descriptors->len; j++) {
|
|
descriptor = g_ptr_array_index (stream->descriptors, j);
|
|
loop_length += descriptor->length + 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
length += network_length + loop_length;
|
|
|
|
/* Max length of NIT section is 1024 bytes */
|
|
g_return_val_if_fail (length <= 1024, FALSE);
|
|
|
|
_packetize_common_section (section, length);
|
|
|
|
data = section->data + 8;
|
|
/* reserved - 4 bit
|
|
network_descriptors_length - 12 bit uimsbf */
|
|
GST_WRITE_UINT16_BE (data, network_length | 0xF000);
|
|
data += 2;
|
|
|
|
_packetize_descriptor_array (nit->descriptors, &data);
|
|
|
|
/* reserved - 4 bit
|
|
transport_stream_loop_length - 12 bit uimsbf */
|
|
GST_WRITE_UINT16_BE (data, loop_length | 0xF000);
|
|
data += 2;
|
|
|
|
if (nit->streams) {
|
|
for (i = 0; i < nit->streams->len; i++) {
|
|
stream = g_ptr_array_index (nit->streams, i);
|
|
/* transport_stream_id - 16 bit uimsbf */
|
|
GST_WRITE_UINT16_BE (data, stream->transport_stream_id);
|
|
data += 2;
|
|
|
|
/* original_network_id - 16 bit uimsbf */
|
|
GST_WRITE_UINT16_BE (data, stream->original_network_id);
|
|
data += 2;
|
|
|
|
/* reserved - 4 bit
|
|
transport_descriptors_length - 12 bit uimsbf
|
|
|
|
Set length to zero, and update in loop */
|
|
pos = data;
|
|
*data++ = 0xF0;
|
|
*data++ = 0x00;
|
|
|
|
_packetize_descriptor_array (stream->descriptors, &data);
|
|
|
|
/* Go back and update the descriptor length */
|
|
GST_WRITE_UINT16_BE (pos, (data - pos - 2) | 0xF000);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_mpegts_section_from_nit:
|
|
* @nit: (transfer full): a #GstMpegtsNIT to create the #GstMpegtsSection from
|
|
*
|
|
* Ownership of @nit is taken. The data in @nit is managed by the #GstMpegtsSection
|
|
*
|
|
* Returns: (transfer full): the #GstMpegtsSection
|
|
*/
|
|
GstMpegtsSection *
|
|
gst_mpegts_section_from_nit (GstMpegtsNIT * nit)
|
|
{
|
|
GstMpegtsSection *section;
|
|
|
|
g_return_val_if_fail (nit != NULL, NULL);
|
|
|
|
if (nit->actual_network)
|
|
section = _gst_mpegts_section_init (0x10,
|
|
GST_MTS_TABLE_ID_NETWORK_INFORMATION_ACTUAL_NETWORK);
|
|
else
|
|
section = _gst_mpegts_section_init (0x10,
|
|
GST_MTS_TABLE_ID_NETWORK_INFORMATION_OTHER_NETWORK);
|
|
|
|
section->subtable_extension = nit->network_id;
|
|
section->cached_parsed = (gpointer) nit;
|
|
section->packetizer = _packetize_nit;
|
|
section->destroy_parsed = (GDestroyNotify) _gst_mpegts_nit_free;
|
|
|
|
return section;
|
|
}
|
|
|
|
/* Service Description Table (SDT) */
|
|
|
|
static GstMpegtsSDTService *
|
|
_gst_mpegts_sdt_service_copy (GstMpegtsSDTService * sdt)
|
|
{
|
|
GstMpegtsSDTService *copy = g_slice_dup (GstMpegtsSDTService, sdt);
|
|
|
|
copy->descriptors = g_ptr_array_ref (sdt->descriptors);
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
_gst_mpegts_sdt_service_free (GstMpegtsSDTService * sdt)
|
|
{
|
|
if (sdt->descriptors)
|
|
g_ptr_array_unref (sdt->descriptors);
|
|
g_slice_free (GstMpegtsSDTService, sdt);
|
|
}
|
|
|
|
G_DEFINE_BOXED_TYPE (GstMpegtsSDTService, gst_mpegts_sdt_service,
|
|
(GBoxedCopyFunc) _gst_mpegts_sdt_service_copy,
|
|
(GFreeFunc) _gst_mpegts_sdt_service_free);
|
|
|
|
static GstMpegtsSDT *
|
|
_gst_mpegts_sdt_copy (GstMpegtsSDT * sdt)
|
|
{
|
|
GstMpegtsSDT *copy = g_slice_dup (GstMpegtsSDT, sdt);
|
|
|
|
copy->services = g_ptr_array_ref (sdt->services);
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
_gst_mpegts_sdt_free (GstMpegtsSDT * sdt)
|
|
{
|
|
g_ptr_array_unref (sdt->services);
|
|
g_slice_free (GstMpegtsSDT, sdt);
|
|
}
|
|
|
|
G_DEFINE_BOXED_TYPE (GstMpegtsSDT, gst_mpegts_sdt,
|
|
(GBoxedCopyFunc) _gst_mpegts_sdt_copy, (GFreeFunc) _gst_mpegts_sdt_free);
|
|
|
|
|
|
static gpointer
|
|
_parse_sdt (GstMpegtsSection * section)
|
|
{
|
|
GstMpegtsSDT *sdt = NULL;
|
|
guint i = 0, allocated_services = 8;
|
|
guint8 *data, *end, *entry_begin;
|
|
guint tmp;
|
|
guint sdt_info_length;
|
|
guint descriptors_loop_length;
|
|
|
|
GST_DEBUG ("SDT");
|
|
|
|
sdt = g_slice_new0 (GstMpegtsSDT);
|
|
|
|
data = section->data;
|
|
end = data + section->section_length;
|
|
|
|
sdt->transport_stream_id = section->subtable_extension;
|
|
|
|
/* Skip common fields */
|
|
data += 8;
|
|
|
|
sdt->original_network_id = GST_READ_UINT16_BE (data);
|
|
data += 2;
|
|
|
|
/* skip reserved byte */
|
|
data += 1;
|
|
|
|
sdt->actual_ts = section->table_id == 0x42;
|
|
|
|
sdt_info_length = section->section_length - 11;
|
|
|
|
sdt->services = g_ptr_array_new_full (allocated_services,
|
|
(GDestroyNotify) _gst_mpegts_sdt_service_free);
|
|
|
|
/* read up to the CRC */
|
|
while (sdt_info_length - 4 > 0) {
|
|
GstMpegtsSDTService *service = g_slice_new0 (GstMpegtsSDTService);
|
|
g_ptr_array_add (sdt->services, service);
|
|
|
|
entry_begin = data;
|
|
|
|
if (sdt_info_length - 4 < 5) {
|
|
/* each entry must be at least 5 bytes (+4 bytes for the CRC) */
|
|
GST_WARNING ("PID %d invalid SDT entry size %d",
|
|
section->pid, sdt_info_length);
|
|
goto error;
|
|
}
|
|
|
|
service->service_id = GST_READ_UINT16_BE (data);
|
|
data += 2;
|
|
|
|
service->EIT_schedule_flag = ((*data & 0x02) == 2);
|
|
service->EIT_present_following_flag = (*data & 0x01) == 1;
|
|
|
|
data += 1;
|
|
tmp = GST_READ_UINT16_BE (data);
|
|
|
|
service->running_status = (*data >> 5) & 0x07;
|
|
service->free_CA_mode = (*data >> 4) & 0x01;
|
|
|
|
descriptors_loop_length = tmp & 0x0FFF;
|
|
data += 2;
|
|
|
|
if (descriptors_loop_length && (data + descriptors_loop_length > end - 4)) {
|
|
GST_WARNING ("PID %d invalid SDT entry %d descriptors loop length %d",
|
|
section->pid, service->service_id, descriptors_loop_length);
|
|
goto error;
|
|
}
|
|
service->descriptors =
|
|
gst_mpegts_parse_descriptors (data, descriptors_loop_length);
|
|
if (!service->descriptors)
|
|
goto error;
|
|
data += descriptors_loop_length;
|
|
|
|
sdt_info_length -= data - entry_begin;
|
|
i += 1;
|
|
}
|
|
|
|
if (data != end - 4) {
|
|
GST_WARNING ("PID %d invalid SDT parsed %d length %d",
|
|
section->pid, (gint) (data - section->data), section->section_length);
|
|
goto error;
|
|
}
|
|
|
|
return sdt;
|
|
|
|
error:
|
|
if (sdt)
|
|
_gst_mpegts_sdt_free (sdt);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* gst_mpegts_section_get_sdt:
|
|
* @section: a #GstMpegtsSection of type %GST_MPEGTS_SECTION_SDT
|
|
*
|
|
* Returns the #GstMpegtsSDT contained in the @section.
|
|
*
|
|
* Returns: The #GstMpegtsSDT contained in the section, or %NULL if an error
|
|
* happened.
|
|
*/
|
|
const GstMpegtsSDT *
|
|
gst_mpegts_section_get_sdt (GstMpegtsSection * section)
|
|
{
|
|
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_SDT, NULL);
|
|
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
|
|
|
|
if (!section->cached_parsed)
|
|
section->cached_parsed =
|
|
__common_section_checks (section, 15, _parse_sdt,
|
|
(GDestroyNotify) _gst_mpegts_sdt_free);
|
|
|
|
return (const GstMpegtsSDT *) section->cached_parsed;
|
|
}
|
|
|
|
/**
|
|
* gst_mpegts_sdt_new:
|
|
*
|
|
* Allocates and initializes a #GstMpegtsSDT.
|
|
*
|
|
* Returns: (transfer full): A newly allocated #GstMpegtsSDT
|
|
*/
|
|
GstMpegtsSDT *
|
|
gst_mpegts_sdt_new (void)
|
|
{
|
|
GstMpegtsSDT *sdt;
|
|
|
|
sdt = g_slice_new0 (GstMpegtsSDT);
|
|
|
|
sdt->services = g_ptr_array_new_with_free_func ((GDestroyNotify)
|
|
_gst_mpegts_sdt_service_free);
|
|
|
|
return sdt;
|
|
}
|
|
|
|
/**
|
|
* gst_mpegts_sdt_service_new:
|
|
*
|
|
* Allocates and initializes a #GstMpegtsSDTService.
|
|
*
|
|
* Returns: (transfer full): A newly allocated #GstMpegtsSDTService
|
|
*/
|
|
GstMpegtsSDTService *
|
|
gst_mpegts_sdt_service_new (void)
|
|
{
|
|
GstMpegtsSDTService *service;
|
|
|
|
service = g_slice_new0 (GstMpegtsSDTService);
|
|
|
|
service->descriptors = g_ptr_array_new_with_free_func ((GDestroyNotify)
|
|
gst_mpegts_descriptor_free);
|
|
|
|
return service;
|
|
}
|
|
|
|
static gboolean
|
|
_packetize_sdt (GstMpegtsSection * section)
|
|
{
|
|
gsize length, service_length;
|
|
const GstMpegtsSDT *sdt;
|
|
GstMpegtsSDTService *service;
|
|
GstMpegtsDescriptor *descriptor;
|
|
guint i, j;
|
|
guint8 *data, *pos;
|
|
|
|
sdt = gst_mpegts_section_get_sdt (section);
|
|
|
|
if (sdt == NULL)
|
|
return FALSE;
|
|
|
|
/* 8 byte common section fields
|
|
2 byte original_network_id
|
|
1 byte reserved
|
|
4 byte CRC */
|
|
length = 15;
|
|
|
|
/* Find length of services */
|
|
service_length = 0;
|
|
if (sdt->services) {
|
|
for (i = 0; i < sdt->services->len; i++) {
|
|
service = g_ptr_array_index (sdt->services, i);
|
|
service_length += 5;
|
|
if (service->descriptors) {
|
|
for (j = 0; j < service->descriptors->len; j++) {
|
|
descriptor = g_ptr_array_index (service->descriptors, j);
|
|
service_length += descriptor->length + 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
length += service_length;
|
|
|
|
/* Max length if SDT section is 1024 bytes */
|
|
g_return_val_if_fail (length <= 1024, FALSE);
|
|
|
|
_packetize_common_section (section, length);
|
|
|
|
data = section->data + 8;
|
|
/* original_network_id - 16 bit uimsbf */
|
|
GST_WRITE_UINT16_BE (data, sdt->original_network_id);
|
|
data += 2;
|
|
/* reserved - 8 bit */
|
|
*data++ = 0xFF;
|
|
|
|
if (sdt->services) {
|
|
for (i = 0; i < sdt->services->len; i++) {
|
|
service = g_ptr_array_index (sdt->services, i);
|
|
/* service_id - 16 bit uimsbf */
|
|
GST_WRITE_UINT16_BE (data, service->service_id);
|
|
data += 2;
|
|
|
|
/* reserved - 6 bit
|
|
EIT_schedule_flag - 1 bit
|
|
EIT_present_following_flag - 1 bit */
|
|
*data = 0xFC;
|
|
if (service->EIT_schedule_flag)
|
|
*data |= 0x02;
|
|
if (service->EIT_present_following_flag)
|
|
*data |= 0x01;
|
|
data++;
|
|
|
|
/* running_status - 3 bit uimsbf
|
|
free_CA_mode - 1 bit
|
|
descriptors_loop_length - 12 bit uimsbf */
|
|
/* Set length to zero for now */
|
|
pos = data;
|
|
*data++ = 0x00;
|
|
*data++ = 0x00;
|
|
|
|
_packetize_descriptor_array (service->descriptors, &data);
|
|
|
|
/* Go back and update the descriptor length */
|
|
GST_WRITE_UINT16_BE (pos, data - pos - 2);
|
|
|
|
*pos |= service->running_status << 5;
|
|
if (service->free_CA_mode)
|
|
*pos |= 0x10;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_mpegts_section_from_sdt:
|
|
* @sdt: (transfer full): a #GstMpegtsSDT to create the #GstMpegtsSection from
|
|
*
|
|
* Ownership of @sdt is taken. The data in @sdt is managed by the #GstMpegtsSection
|
|
*
|
|
* Returns: (transfer full): the #GstMpegtsSection
|
|
*/
|
|
GstMpegtsSection *
|
|
gst_mpegts_section_from_sdt (GstMpegtsSDT * sdt)
|
|
{
|
|
GstMpegtsSection *section;
|
|
|
|
g_return_val_if_fail (sdt != NULL, NULL);
|
|
|
|
if (sdt->actual_ts)
|
|
section = _gst_mpegts_section_init (0x11,
|
|
GST_MTS_TABLE_ID_SERVICE_DESCRIPTION_ACTUAL_TS);
|
|
else
|
|
section = _gst_mpegts_section_init (0x11,
|
|
GST_MTS_TABLE_ID_SERVICE_DESCRIPTION_OTHER_TS);
|
|
|
|
section->subtable_extension = sdt->transport_stream_id;
|
|
section->cached_parsed = (gpointer) sdt;
|
|
section->packetizer = _packetize_sdt;
|
|
section->destroy_parsed = (GDestroyNotify) _gst_mpegts_sdt_free;
|
|
|
|
return section;
|
|
}
|
|
|
|
/* Time and Date Table (TDT) */
|
|
static gpointer
|
|
_parse_tdt (GstMpegtsSection * section)
|
|
{
|
|
return (gpointer) _parse_utc_time (section->data + 3);
|
|
}
|
|
|
|
/**
|
|
* gst_mpegts_section_get_tdt:
|
|
* @section: a #GstMpegtsSection of type %GST_MPEGTS_SECTION_TDT
|
|
*
|
|
* Returns the #GstDateTime of the TDT
|
|
*
|
|
* Returns: The #GstDateTime contained in the section, or %NULL
|
|
* if an error happened. Release with #gst_date_time_unref when done.
|
|
*/
|
|
GstDateTime *
|
|
gst_mpegts_section_get_tdt (GstMpegtsSection * section)
|
|
{
|
|
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_TDT, NULL);
|
|
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
|
|
|
|
if (!section->cached_parsed)
|
|
section->cached_parsed =
|
|
__common_section_checks (section, 8, _parse_tdt,
|
|
(GDestroyNotify) gst_date_time_unref);
|
|
|
|
if (section->cached_parsed)
|
|
return gst_date_time_ref ((GstDateTime *) section->cached_parsed);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Time Offset Table (TOT) */
|
|
static GstMpegtsTOT *
|
|
_gst_mpegts_tot_copy (GstMpegtsTOT * tot)
|
|
{
|
|
GstMpegtsTOT *copy = g_slice_dup (GstMpegtsTOT, tot);
|
|
|
|
if (tot->utc_time)
|
|
copy->utc_time = gst_date_time_ref (tot->utc_time);
|
|
copy->descriptors = g_ptr_array_ref (tot->descriptors);
|
|
|
|
return copy;
|
|
}
|
|
|
|
static void
|
|
_gst_mpegts_tot_free (GstMpegtsTOT * tot)
|
|
{
|
|
if (tot->utc_time)
|
|
gst_date_time_unref (tot->utc_time);
|
|
if (tot->descriptors)
|
|
g_ptr_array_unref (tot->descriptors);
|
|
g_slice_free (GstMpegtsTOT, tot);
|
|
}
|
|
|
|
G_DEFINE_BOXED_TYPE (GstMpegtsTOT, gst_mpegts_tot,
|
|
(GBoxedCopyFunc) _gst_mpegts_tot_copy, (GFreeFunc) _gst_mpegts_tot_free);
|
|
|
|
static gpointer
|
|
_parse_tot (GstMpegtsSection * section)
|
|
{
|
|
guint8 *data;
|
|
GstMpegtsTOT *tot;
|
|
guint16 desc_len;
|
|
|
|
GST_DEBUG ("TOT");
|
|
|
|
tot = g_slice_new0 (GstMpegtsTOT);
|
|
|
|
tot->utc_time = _parse_utc_time (section->data + 3);
|
|
|
|
/* Skip 5 bytes from utc_time (+3 of initial offset) */
|
|
data = section->data + 8;
|
|
|
|
desc_len = GST_READ_UINT16_BE (data) & 0xFFF;
|
|
data += 2;
|
|
tot->descriptors = gst_mpegts_parse_descriptors (data, desc_len);
|
|
|
|
return (gpointer) tot;
|
|
}
|
|
|
|
/**
|
|
* gst_mpegts_section_get_tot:
|
|
* @section: a #GstMpegtsSection of type %GST_MPEGTS_SECTION_TOT
|
|
*
|
|
* Returns the #GstMpegtsTOT contained in the @section.
|
|
*
|
|
* Returns: The #GstMpegtsTOT contained in the section, or %NULL if an error
|
|
* happened.
|
|
*/
|
|
const GstMpegtsTOT *
|
|
gst_mpegts_section_get_tot (GstMpegtsSection * section)
|
|
{
|
|
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_TOT, NULL);
|
|
g_return_val_if_fail (section->cached_parsed || section->data, NULL);
|
|
|
|
if (!section->cached_parsed)
|
|
section->cached_parsed =
|
|
__common_section_checks (section, 14, _parse_tot,
|
|
(GDestroyNotify) _gst_mpegts_tot_free);
|
|
|
|
return (const GstMpegtsTOT *) section->cached_parsed;
|
|
}
|