mpegts: Add support for SCTE-35 sections

Not all commands are supported, but the most common ones are.
Both parsing and packetizing is supported
This commit is contained in:
Edward Hervey 2019-09-26 17:28:27 +02:00 committed by Edward Hervey
parent 5464775aef
commit 6a9108884c
8 changed files with 1021 additions and 8 deletions

View file

@ -0,0 +1,659 @@
/*
* gst-scte-section.c -
* Copyright (C) 2019 Centricular ltd
* Author: Edward Hervey <edward@centricular.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"
#define MPEGTIME_TO_GSTTIME(t) ((t) * (guint64)100000 / 9)
/**
* SECTION:gst-scte-section
* @title: SCTE variants of MPEG-TS sections
* @short_description: Sections for the various SCTE specifications
* @include: gst/mpegts/mpegts.h
*
*/
/* Splice Information Table (SIT) */
static GstMpegtsSCTESpliceEvent *
_gst_mpegts_scte_splice_event_copy (GstMpegtsSCTESpliceEvent * event)
{
return g_slice_dup (GstMpegtsSCTESpliceEvent, event);
}
static void
_gst_mpegts_scte_splice_event_free (GstMpegtsSCTESpliceEvent * event)
{
g_slice_free (GstMpegtsSCTESpliceEvent, event);
}
G_DEFINE_BOXED_TYPE (GstMpegtsSCTESpliceEvent, gst_mpegts_scte_splice_event,
(GBoxedCopyFunc) _gst_mpegts_scte_splice_event_copy,
(GFreeFunc) _gst_mpegts_scte_splice_event_free);
static GstMpegtsSCTESpliceEvent *
_parse_slice_event (guint8 ** orig_data, guint8 * end, gboolean insert_event)
{
GstMpegtsSCTESpliceEvent *event = g_slice_new0 (GstMpegtsSCTESpliceEvent);
guint8 *data = *orig_data;
/* Note : +6 is because of the final descriptor_loop_length and CRC */
if (data + 5 + 6 > end)
goto error;
event->insert_event = insert_event;
event->splice_event_id = GST_READ_UINT32_BE (data);
GST_LOG ("splice_event_id: 0x%08x", event->splice_event_id);
data += 4;
event->splice_event_cancel_indicator = *data >> 7;
GST_LOG ("splice_event_cancel_indicator: %d",
event->splice_event_cancel_indicator);
data += 1;
GST_DEBUG ("data %p", data);
if (event->splice_event_cancel_indicator == 0) {
if (data + 5 + 6 > end)
goto error;
event->out_of_network_indicator = *data >> 7;
event->program_splice_flag = (*data >> 6) & 0x01;
event->duration_flag = (*data >> 5) & 0x01;
event->splice_immediate_flag = (*data >> 4) & 0x01;
GST_LOG ("out_of_network_indicator:%d", event->out_of_network_indicator);
GST_LOG ("program_splice_flag:%d", event->program_splice_flag);
GST_LOG ("duration_flag:%d", event->duration_flag);
GST_LOG ("splice_immediate_flag:%d", event->splice_immediate_flag);
data += 1;
if (event->program_splice_flag == 0) {
GST_ERROR ("Component splice flag not supported !");
goto error;
}
if (event->splice_immediate_flag == 0) {
event->program_splice_time_specified = *data >> 7;
if (event->program_splice_time_specified) {
event->program_splice_time = ((guint64) (*data & 0x01)) << 32;
data += 1;
event->program_splice_time += GST_READ_UINT32_BE (data);
data += 4;
GST_LOG ("program_splice_time %" G_GUINT64_FORMAT " (%" GST_TIME_FORMAT
")", event->program_splice_time,
GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (event->program_splice_time)));
} else
data += 1;
}
if (event->duration_flag) {
event->break_duration_auto_return = *data >> 7;
event->break_duration = ((guint64) (*data & 0x01)) << 32;
data += 1;
event->break_duration += GST_READ_UINT32_BE (data);
data += 4;
GST_LOG ("break_duration_auto_return:%d",
event->break_duration_auto_return);
GST_LOG ("break_duration %" G_GUINT64_FORMAT " (%" GST_TIME_FORMAT ")",
event->break_duration,
GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (event->break_duration)));
}
event->unique_program_id = GST_READ_UINT16_BE (data);
GST_LOG ("unique_program_id:%" G_GUINT16_FORMAT, event->unique_program_id);
data += 2;
event->avail_num = *data++;
event->avails_expected = *data++;
GST_LOG ("avail %d/%d", event->avail_num, event->avails_expected);
}
GST_DEBUG ("done");
*orig_data = data;
return event;
error:
{
if (event)
_gst_mpegts_scte_splice_event_free (event);
return NULL;
}
}
static GstMpegtsSCTESIT *
_gst_mpegts_scte_sit_copy (GstMpegtsSCTESIT * sit)
{
GstMpegtsSCTESIT *copy = g_slice_dup (GstMpegtsSCTESIT, sit);
copy->splices = g_ptr_array_ref (sit->splices);
copy->descriptors = g_ptr_array_ref (sit->descriptors);
return copy;
}
static void
_gst_mpegts_scte_sit_free (GstMpegtsSCTESIT * sit)
{
g_ptr_array_unref (sit->splices);
g_ptr_array_unref (sit->descriptors);
g_slice_free (GstMpegtsSCTESIT, sit);
}
G_DEFINE_BOXED_TYPE (GstMpegtsSCTESIT, gst_mpegts_scte_sit,
(GBoxedCopyFunc) _gst_mpegts_scte_sit_copy,
(GFreeFunc) _gst_mpegts_scte_sit_free);
static gpointer
_parse_sit (GstMpegtsSection * section)
{
GstMpegtsSCTESIT *sit = NULL;
guint8 *data, *end;
guint32 tmp;
GST_DEBUG ("SIT");
/* Even if the section is not a short one, it still uses CRC */
if (_calc_crc32 (section->data, section->section_length) != 0) {
GST_WARNING ("PID:0x%04x table_id:0x%02x, Bad CRC on section", section->pid,
section->table_id);
return NULL;
}
sit = g_slice_new0 (GstMpegtsSCTESIT);
data = section->data;
end = data + section->section_length;
GST_MEMDUMP ("section", data, section->section_length);
/* Skip already-checked fields */
data += 3;
/* Ensure protocol_version is 0 */
if (*data != 0) {
GST_WARNING ("Unsupported SCTE SIT protocol version (%d)", *data);
goto error;
}
data += 1;
/* encryption */
sit->encrypted_packet = (*data) >> 7;
sit->encryption_algorithm = (*data) & 0x3f;
sit->pts_adjustment = ((guint64) (*data & 0x01)) << 32;
data += 1;
sit->pts_adjustment += GST_READ_UINT32_BE (data);
data += 4;
sit->cw_index = *data;
data += 1;
tmp = GST_READ_UINT24_BE (data);
data += 3;
sit->tier = (tmp >> 12);
sit->splice_command_length = tmp & 0xfff;
/* 0xfff is for backwards compatibility when reading */
if (sit->splice_command_length == 0xfff)
sit->splice_command_length = 0;
GST_LOG ("command length %d", sit->splice_command_length);
sit->splice_command_type = *data;
data += 1;
if (sit->splice_command_length
&& (data + sit->splice_command_length > end - 4)) {
GST_WARNING ("PID %d invalid SCTE SIT splice command length %d",
section->pid, sit->splice_command_length);
goto error;
}
sit->splices = g_ptr_array_new_with_free_func ((GDestroyNotify)
_gst_mpegts_scte_splice_event_free);
switch (sit->splice_command_type) {
case GST_MTS_SCTE_SPLICE_COMMAND_NULL:
case GST_MTS_SCTE_SPLICE_COMMAND_BANDWIDTH:
/* These commands have no payload */
break;
case GST_MTS_SCTE_SPLICE_COMMAND_TIME:
{
sit->splice_time_specified = (*data >> 7);
if (sit->splice_time_specified) {
sit->splice_time = ((guint64) (*data & 0x01)) << 32;
data += 1;
sit->splice_time += GST_READ_UINT32_BE (data);
data += 4;
} else
data += 1;
}
break;
case GST_MTS_SCTE_SPLICE_COMMAND_INSERT:
{
GstMpegtsSCTESpliceEvent *event = _parse_slice_event (&data, end, TRUE);
if (event == NULL)
goto error;
g_ptr_array_add (sit->splices, event);
}
break;
case GST_MTS_SCTE_SPLICE_COMMAND_PRIVATE:
{
GST_FIXME ("Implement SCTE-35 private commands");
data += sit->splice_command_length;
}
break;
default:
GST_ERROR ("Unknown SCTE splice command type (0x%02x) !",
sit->splice_command_type);
goto error;
}
/* descriptors */
tmp = GST_READ_UINT16_BE (data);
data += 2;
GST_MEMDUMP ("desc ?", data, tmp);
sit->descriptors = gst_mpegts_parse_descriptors (data, tmp);
if (!sit->descriptors) {
GST_DEBUG ("no descriptors %d", tmp);
goto error;
}
data += tmp;
GST_DEBUG ("%p - %p", data, end);
if (data != end - 4) {
GST_WARNING ("PID %d invalid SIT parsed %d length %d",
section->pid, (gint) (data - section->data), section->section_length);
goto error;
}
return sit;
error:
if (sit)
_gst_mpegts_scte_sit_free (sit);
return NULL;
}
/**
* gst_mpegts_section_get_scte_sit:
* @section: a #GstMpegtsSection of type %GST_MPEGTS_SECTION_SCTE_SIT
*
* Returns the #GstMpegtsSCTESIT contained in the @section.
*
* Returns: The #GstMpegtsSCTESIT contained in the section, or %NULL if an error
* happened.
*/
const GstMpegtsSCTESIT *
gst_mpegts_section_get_scte_sit (GstMpegtsSection * section)
{
g_return_val_if_fail (section->section_type == GST_MPEGTS_SECTION_SCTE_SIT,
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_sit,
(GDestroyNotify) _gst_mpegts_scte_sit_free);
return (const GstMpegtsSCTESIT *) section->cached_parsed;
}
/**
* gst_mpegts_scte_sit_new:
*
* Allocates and initializes a #GstMpegtsSCTESIT.
*
* Returns: (transfer full): A newly allocated #GstMpegtsSCTESIT
*/
GstMpegtsSCTESIT *
gst_mpegts_scte_sit_new (void)
{
GstMpegtsSCTESIT *sit;
sit = g_slice_new0 (GstMpegtsSCTESIT);
/* Set all default values (which aren't already 0/NULL) */
sit->tier = 0xfff;
sit->splices = g_ptr_array_new_with_free_func ((GDestroyNotify)
_gst_mpegts_scte_splice_event_free);
sit->descriptors = g_ptr_array_new_with_free_func ((GDestroyNotify)
gst_mpegts_descriptor_free);
return sit;
}
/**
* gst_mpegts_scte_null_new:
*
* Allocates and initializes a NULL command #GstMpegtsSCTESIT.
*
* Returns: (transfer full): A newly allocated #GstMpegtsSCTESIT
*/
GstMpegtsSCTESIT *
gst_mpegts_scte_null_new (void)
{
GstMpegtsSCTESIT *sit = gst_mpegts_scte_sit_new ();
sit->splice_command_type = GST_MTS_SCTE_SPLICE_COMMAND_NULL;
return sit;
}
/**
* gst_mpegts_scte_cancel_new:
* @event_id: The event ID to cancel.
*
* Allocates and initializes a new INSERT command #GstMpegtsSCTESIT
* setup to cancel the specified @event_id.
*
* Returns: (transfer full): A newly allocated #GstMpegtsSCTESIT
*/
GstMpegtsSCTESIT *
gst_mpegts_scte_cancel_new (guint32 event_id)
{
GstMpegtsSCTESIT *sit = gst_mpegts_scte_sit_new ();
GstMpegtsSCTESpliceEvent *event = gst_mpegts_scte_splice_event_new ();
sit->splice_command_type = GST_MTS_SCTE_SPLICE_COMMAND_INSERT;
event->splice_event_id = event_id;
event->splice_event_cancel_indicator = TRUE;
g_ptr_array_add (sit->splices, event);
return sit;
}
/**
* gst_mpegts_scte_splice_in_new:
* @event_id: The event ID.
* @splice_time: The PCR time for the splice event
*
* Allocates and initializes a new "Splice In" INSERT command
* #GstMpegtsSCTESIT for the given @event_id and @splice_time.
*
* If the @splice_time is #G_MAXUINT64 then the event will be
* immediate as opposed to for the target @splice_time.
*
* Returns: (transfer full): A newly allocated #GstMpegtsSCTESIT
*/
GstMpegtsSCTESIT *
gst_mpegts_scte_splice_in_new (guint32 event_id, guint64 splice_time)
{
GstMpegtsSCTESIT *sit = gst_mpegts_scte_sit_new ();
GstMpegtsSCTESpliceEvent *event = gst_mpegts_scte_splice_event_new ();
sit->splice_command_type = GST_MTS_SCTE_SPLICE_COMMAND_INSERT;
event->splice_event_id = event_id;
if (splice_time == G_MAXUINT64) {
event->splice_immediate_flag = TRUE;
} else {
event->program_splice_time_specified = TRUE;
event->program_splice_time = splice_time;
}
g_ptr_array_add (sit->splices, event);
return sit;
}
/**
* gst_mpegts_scte_splice_out_new:
* @event_id: The event ID.
* @splice_time: The PCR time for the splice event
* @duration: The optional duration.
*
* Allocates and initializes a new "Splice Out" INSERT command
* #GstMpegtsSCTESIT for the given @event_id, @splice_time and
* duration.
*
* If the @splice_time is #G_MAXUINT64 then the event will be
* immediate as opposed to for the target @splice_time.
*
* If the @duration is 0 it won't be specified in the event.
*
* Returns: (transfer full): A newly allocated #GstMpegtsSCTESIT
*/
GstMpegtsSCTESIT *
gst_mpegts_scte_splice_out_new (guint32 event_id, guint64 splice_time,
guint64 duration)
{
GstMpegtsSCTESIT *sit = gst_mpegts_scte_sit_new ();
GstMpegtsSCTESpliceEvent *event = gst_mpegts_scte_splice_event_new ();
sit->splice_command_type = GST_MTS_SCTE_SPLICE_COMMAND_INSERT;
event->splice_event_id = event_id;
event->out_of_network_indicator = TRUE;
if (splice_time == G_MAXUINT64) {
event->splice_immediate_flag = TRUE;
} else {
event->program_splice_time_specified = TRUE;
event->program_splice_time = splice_time;
}
if (duration != 0) {
event->duration_flag = TRUE;
event->break_duration = duration;
}
g_ptr_array_add (sit->splices, event);
return sit;
}
/**
* gst_mpegts_scte_splice_event_new:
*
* Allocates and initializes a #GstMpegtsSCTESpliceEvent.
*
* Returns: (transfer full): A newly allocated #GstMpegtsSCTESpliceEvent
*/
GstMpegtsSCTESpliceEvent *
gst_mpegts_scte_splice_event_new (void)
{
GstMpegtsSCTESpliceEvent *event = g_slice_new0 (GstMpegtsSCTESpliceEvent);
/* Non-0 Default values */
event->program_splice_flag = TRUE;
return event;
}
static gboolean
_packetize_sit (GstMpegtsSection * section)
{
gsize length, command_length, descriptor_length;
const GstMpegtsSCTESIT *sit;
GstMpegtsDescriptor *descriptor;
guint32 tmp32;
guint i;
guint8 *data;
sit = gst_mpegts_section_get_scte_sit (section);
if (sit == NULL)
return FALSE;
/* Skip cases we don't handle for now */
if (sit->encrypted_packet) {
GST_WARNING ("SCTE encrypted packet is not supported");
return FALSE;
}
switch (sit->splice_command_type) {
case GST_MTS_SCTE_SPLICE_COMMAND_SCHEDULE:
case GST_MTS_SCTE_SPLICE_COMMAND_TIME:
case GST_MTS_SCTE_SPLICE_COMMAND_PRIVATE:
GST_WARNING ("SCTE command not supported");
return FALSE;
default:
break;
}
/* Smallest splice section are the NULL and bandwith command:
* 14 bytes for the header
* 0 bytes for the command
* 2 bytes for the empty descriptor loop length
* 4 bytes for the CRC */
length = 20;
command_length = 0;
/* Add the size of splices */
for (i = 0; i < sit->splices->len; i++) {
GstMpegtsSCTESpliceEvent *event = g_ptr_array_index (sit->splices, i);
/* There is at least 5 bytes */
command_length += 5;
if (!event->splice_event_cancel_indicator) {
if (!event->program_splice_flag) {
GST_WARNING ("Only SCTE program splices are supported");
return FALSE;
}
/* Add at least 5 bytes for common fields */
command_length += 5;
if (!event->splice_immediate_flag) {
if (event->program_splice_time_specified)
command_length += 5;
else
command_length += 1;
}
if (event->duration_flag)
command_length += 5;
}
}
length += command_length;
/* Calculate size of descriptors */
descriptor_length = 0;
for (i = 0; i < sit->descriptors->len; i++) {
descriptor = g_ptr_array_index (sit->descriptors, i);
descriptor_length += descriptor->length + 2;
}
length += descriptor_length;
/* Max length of SIT section is 4093 bytes */
g_return_val_if_fail (length <= 4093, FALSE);
_packetize_common_section (section, length);
data = section->data + 3;
/* Protocol version (default 0) */
*data++ = 0;
/* 7bits for encryption (not supported : 0) */
/* 33bits for pts_adjustment */
*data++ = (sit->pts_adjustment) >> 32 & 0x01;
GST_WRITE_UINT32_BE (data, sit->pts_adjustment & 0xffffffff);
data += 4;
/* cw_index : 8 bit */
*data++ = sit->cw_index;
/* tier : 12bit
* splice_command_length : 12bit
* splice_command_type : 8 bit */
tmp32 = (sit->tier & 0xfff) << 20;
tmp32 |= (command_length & 0xfff) << 8;
tmp32 |= sit->splice_command_type;
GST_WRITE_UINT32_BE (data, tmp32);
data += 4;
/* Write the events */
for (i = 0; i < sit->splices->len; i++) {
GstMpegtsSCTESpliceEvent *event = g_ptr_array_index (sit->splices, i);
/* splice_event_id : 32bit */
GST_WRITE_UINT32_BE (data, event->splice_event_id);
data += 4;
/* splice_event_cancel_indicator : 1bit
* reserved ; 7bit */
*data++ = event->splice_event_cancel_indicator ? 0xff : 0x7f;
if (!event->splice_event_cancel_indicator) {
/* out_of_network_indicator : 1bit
* program_splice_flag : 1bit
* duration_flag : 1bit
* splice_immediate_flag : 1bit
* reserved : 4bit */
*data++ = (event->out_of_network_indicator << 7) |
(event->program_splice_flag << 6) |
(event->duration_flag << 5) |
(event->splice_immediate_flag << 4) | 0x0f;
if (!event->splice_immediate_flag) {
/* program_splice_time_specified : 1bit
* reserved : 6/7 bit */
if (!event->program_splice_time_specified)
*data++ = 0x7f;
else {
/* time : 33bit */
*data++ = 0xf2 | ((event->program_splice_time >> 32) & 0x1);
GST_WRITE_UINT32_BE (data, event->program_splice_time & 0xffffffff);
data += 4;
}
}
if (event->duration_flag) {
*data = event->break_duration_auto_return ? 0xfe : 0x7e;
*data++ |= (event->break_duration >> 32) & 0x1;
GST_WRITE_UINT32_BE (data, event->break_duration & 0xffffffff);
data += 4;
}
/* unique_program_id : 16bit */
GST_WRITE_UINT16_BE (data, event->unique_program_id);
data += 2;
/* avail_num : 8bit */
*data++ = event->avail_num;
/* avails_expected : 8bit */
*data++ = event->avails_expected;
}
}
/* Descriptors */
GST_WRITE_UINT16_BE (data, descriptor_length);
data += 2;
_packetize_descriptor_array (sit->descriptors, &data);
/* CALCULATE AND WRITE THE CRC ! */
GST_WRITE_UINT32_BE (data, _calc_crc32 (section->data, data - section->data));
return TRUE;
}
/**
* gst_mpegts_section_from_scte_sit:
* @sit: (transfer full): a #GstMpegtsSCTESIT to create the #GstMpegtsSection from
*
* Ownership of @sit is taken. The data in @sit is managed by the #GstMpegtsSection
*
* Returns: (transfer full): the #GstMpegtsSection
*/
GstMpegtsSection *
gst_mpegts_section_from_scte_sit (GstMpegtsSCTESIT * sit, guint16 pid)
{
GstMpegtsSection *section;
g_return_val_if_fail (sit != NULL, NULL);
section = _gst_mpegts_section_init (pid, GST_MTS_TABLE_ID_SCTE_SPLICE);
section->short_section = TRUE;
section->cached_parsed = (gpointer) sit;
section->packetizer = _packetize_sit;
section->destroy_parsed = (GDestroyNotify) _gst_mpegts_scte_sit_free;
section->short_section = TRUE;
return section;
}

View file

@ -34,6 +34,7 @@ G_BEGIN_DECLS
* GstMpegtsScteStreamType: * GstMpegtsScteStreamType:
* @GST_MPEGTS_STREAM_TYPE_SCTE_SUBTITLING: SCTE-27 Subtitling * @GST_MPEGTS_STREAM_TYPE_SCTE_SUBTITLING: SCTE-27 Subtitling
* @GST_MPEGTS_STREAM_TYPE_SCTE_ISOCH_DATA: SCTE-19 Isochronous data * @GST_MPEGTS_STREAM_TYPE_SCTE_ISOCH_DATA: SCTE-19 Isochronous data
* @GST_MPEGTS_STREAM_TYPE_SCTE_SIT: SCTE-35 Splice Information Table
* @GST_MPEGTS_STREAM_TYPE_SCTE_DST_NRT: SCTE-07 Data Service or * @GST_MPEGTS_STREAM_TYPE_SCTE_DST_NRT: SCTE-07 Data Service or
* Network Resource Table * Network Resource Table
* @GST_MPEGTS_STREAM_TYPE_SCTE_DSMCC_DCB: Type B - DSM-CC Data Carousel * @GST_MPEGTS_STREAM_TYPE_SCTE_DSMCC_DCB: Type B - DSM-CC Data Carousel
@ -51,7 +52,9 @@ typedef enum {
/* 0x01 - 0x82 : defined in other specs */ /* 0x01 - 0x82 : defined in other specs */
GST_MPEGTS_STREAM_TYPE_SCTE_SUBTITLING = 0x82, /* Subtitling data */ GST_MPEGTS_STREAM_TYPE_SCTE_SUBTITLING = 0x82, /* Subtitling data */
GST_MPEGTS_STREAM_TYPE_SCTE_ISOCH_DATA = 0x83, /* Isochronous data */ GST_MPEGTS_STREAM_TYPE_SCTE_ISOCH_DATA = 0x83, /* Isochronous data */
/* 0x84 - 0x94 : defined in other specs */ /* 0x84 - 0x85 : defined in other specs */
GST_MPEGTS_STREAM_TYPE_SCTE_SIT = 0x86, /* Splice Information Table */
/* 0x87 - 0x94 : defined in other specs */
GST_MPEGTS_STREAM_TYPE_SCTE_DST_NRT = 0x95, /* DST / NRT data */ GST_MPEGTS_STREAM_TYPE_SCTE_DST_NRT = 0x95, /* DST / NRT data */
/* 0x96 - 0xaf : defined in other specs */ /* 0x96 - 0xaf : defined in other specs */
GST_MPEGTS_STREAM_TYPE_SCTE_DSMCC_DCB = 0xb0, /* Data Carousel Type B */ GST_MPEGTS_STREAM_TYPE_SCTE_DSMCC_DCB = 0xb0, /* Data Carousel Type B */
@ -97,6 +100,117 @@ typedef enum {
} GstMpegtsSectionSCTETableID; } GstMpegtsSectionSCTETableID;
/* Splice Information Table (SIT) */
#define GST_TYPE_MPEGTS_SCTE_SPLICE_EVENT (gst_mpegts_scte_splice_event_get_type);
typedef struct _GstMpegtsSCTESpliceEvent GstMpegtsSCTESpliceEvent;
struct _GstMpegtsSCTESpliceEvent {
/* TRUE if from/to an insert event (else belongs to a schedule event) */
gboolean insert_event;
guint32 splice_event_id;
gboolean splice_event_cancel_indicator;
/* If splice_event_cancel_indicator == 0 */
gboolean out_of_network_indicator;
gboolean program_splice_flag; /* NOTE: Only program splice are supported */
gboolean duration_flag;
gboolean splice_immediate_flag; /* Only valid for insert_event */
gboolean program_splice_time_specified;
guint64 program_splice_time;
gboolean break_duration_auto_return;
guint64 break_duration;
guint16 unique_program_id;
guint8 avail_num;
guint8 avails_expected;
};
/*
* Types of descriptors
*
* Note: These are only for the descriptors *WITHIN* a SIT */
typedef enum {
GST_MTS_SCTE_DESC_AVAIL = 0x00,
GST_MTS_SCTE_DESC_DTMF = 0x01,
GST_MTS_SCTE_DESC_SEGMENTATION = 0x02,
GST_MTS_SCTE_DESC_TIME = 0x03,
GST_MTS_SCTE_DESC_AUDIO = 0x04
} GstMpegtsSCTESpliceDescriptor;
typedef enum {
GST_MTS_SCTE_SPLICE_COMMAND_NULL = 0x00,
GST_MTS_SCTE_SPLICE_COMMAND_SCHEDULE = 0x04,
GST_MTS_SCTE_SPLICE_COMMAND_INSERT = 0x05,
GST_MTS_SCTE_SPLICE_COMMAND_TIME = 0x06,
GST_MTS_SCTE_SPLICE_COMMAND_BANDWIDTH = 0x07,
GST_MTS_SCTE_SPLICE_COMMAND_PRIVATE = 0xff
} GstMpegtsSCTESpliceCommandType;
#define GST_TYPE_MPEGTS_SCTE_SIT (gst_mpegts_scte_sit_get_type());
typedef struct _GstMpegtsSCTESIT GstMpegtsSCTESIT;
struct _GstMpegtsSCTESIT
{
/* Encryption not supported for now */
gboolean encrypted_packet;
guint8 encryption_algorithm;
guint64 pts_adjustment;
guint8 cw_index;
guint16 tier;
guint16 splice_command_length;
GstMpegtsSCTESpliceCommandType splice_command_type;
/* For time_signal commands */
gboolean splice_time_specified;
guint64 splice_time;
GPtrArray *splices;
GPtrArray *descriptors;
};
GST_MPEGTS_API
GType gst_mpegts_scte_sit_get_type (void);
GST_MPEGTS_API
GstMpegtsSCTESIT *gst_mpegts_scte_sit_new (void);
GST_MPEGTS_API
GstMpegtsSCTESIT *gst_mpegts_scte_null_new (void);
GST_MPEGTS_API
GstMpegtsSCTESIT *gst_mpegts_scte_cancel_new (guint32 event_id);
GST_MPEGTS_API
GstMpegtsSCTESIT *gst_mpegts_scte_splice_in_new (guint32 event_id,
guint64 splice_time);
GST_MPEGTS_API
GstMpegtsSCTESIT *gst_mpegts_scte_splice_out_new (guint32 event_id,
guint64 splice_time,
guint64 duration);
GST_MPEGTS_API
GType gst_mpegts_scte_splice_event_get_type (void);
GST_MPEGTS_API
GstMpegtsSCTESpliceEvent *gst_mpegts_scte_splice_event_new (void);
GST_MPEGTS_API
const GstMpegtsSCTESIT *gst_mpegts_section_get_scte_sit (GstMpegtsSection *section);
GST_MPEGTS_API
GstMpegtsSection *gst_mpegts_section_from_scte_sit (GstMpegtsSCTESIT * sit, guint16 pid);
G_END_DECLS G_END_DECLS
#endif /* GST_SCTE_SECTION_H */ #endif /* GST_SCTE_SECTION_H */

View file

@ -1126,6 +1126,9 @@ _identify_section (guint16 pid, guint8 table_id)
if (pid == 0x1ffb) if (pid == 0x1ffb)
return GST_MPEGTS_SECTION_ATSC_RRT; return GST_MPEGTS_SECTION_ATSC_RRT;
break; break;
case GST_MTS_TABLE_ID_SCTE_SPLICE:
return GST_MPEGTS_SECTION_SCTE_SIT;
break;
default: default:
/* Handle ranges */ /* Handle ranges */
if (table_id >= GST_MTS_TABLE_ID_EVENT_INFORMATION_ACTUAL_TS_PRESENT && if (table_id >= GST_MTS_TABLE_ID_EVENT_INFORMATION_ACTUAL_TS_PRESENT &&
@ -1176,8 +1179,13 @@ _packetize_common_section (GstMpegtsSection * section, gsize length)
case GST_MPEGTS_SECTION_PMT: case GST_MPEGTS_SECTION_PMT:
case GST_MPEGTS_SECTION_CAT: case GST_MPEGTS_SECTION_CAT:
case GST_MPEGTS_SECTION_TSDT: case GST_MPEGTS_SECTION_TSDT:
case GST_MPEGTS_SECTION_SCTE_SIT:
/* Tables from ISO/IEC 13818-1 has a '0' bit /* Tables from ISO/IEC 13818-1 has a '0' bit
* after the section_syntax_indicator */ * after the section_syntax_indicator */
/* FIXME : that 'bit' after the section_syntax_indicator is the
* private_indicator field. We should figure out why/when it
* needs to be set *OR* decide that by default it is set to 0
* (i.e. not private data) unless the user/caller decides */
GST_WRITE_UINT16_BE (data, (section->section_length - 3) | 0x3000); GST_WRITE_UINT16_BE (data, (section->section_length - 3) | 0x3000);
break; break;
default: default:

View file

@ -58,6 +58,7 @@ GType gst_mpegts_section_get_type (void);
* @GST_MPEGTS_SECTION_ATSC_ETT: ATSC Extended Text Table (A65) * @GST_MPEGTS_SECTION_ATSC_ETT: ATSC Extended Text Table (A65)
* @GST_MPEGTS_SECTION_ATSC_EIT: ATSC Event Information Table (A65) * @GST_MPEGTS_SECTION_ATSC_EIT: ATSC Event Information Table (A65)
* @GST_MPEGTS_SECTION_ATSC_STT: ATSC System Time Table (A65) * @GST_MPEGTS_SECTION_ATSC_STT: ATSC System Time Table (A65)
* @GST_MPEGTS_SECTION_SCTE_SIT: SCTE Splice Information Table (SCTE-35)
* *
* Types of #GstMpegtsSection that the library handles. * Types of #GstMpegtsSection that the library handles.
*/ */
@ -79,7 +80,8 @@ typedef enum {
GST_MPEGTS_SECTION_ATSC_ETT, GST_MPEGTS_SECTION_ATSC_ETT,
GST_MPEGTS_SECTION_ATSC_EIT, GST_MPEGTS_SECTION_ATSC_EIT,
GST_MPEGTS_SECTION_ATSC_STT, GST_MPEGTS_SECTION_ATSC_STT,
GST_MPEGTS_SECTION_ATSC_RRT GST_MPEGTS_SECTION_ATSC_RRT,
GST_MPEGTS_SECTION_SCTE_SIT
} GstMpegtsSectionType; } GstMpegtsSectionType;
/** /**

View file

@ -4,6 +4,7 @@ mpegts_sources = [
'gst-dvb-descriptor.c', 'gst-dvb-descriptor.c',
'gst-dvb-section.c', 'gst-dvb-section.c',
'gst-atsc-section.c', 'gst-atsc-section.c',
'gst-scte-section.c',
] ]
mpegts_headers = [ mpegts_headers = [

View file

@ -688,7 +688,8 @@ mpegts_base_update_program (MpegTSBase * base, MpegTSBaseProgram * program,
static gboolean static gboolean
_stream_is_private_section (GstMpegtsPMTStream * stream) _stream_is_private_section (const GstMpegtsPMT * pmt,
GstMpegtsPMTStream * stream)
{ {
switch (stream->stream_type) { switch (stream->stream_type) {
case GST_MPEGTS_STREAM_TYPE_SCTE_DSMCC_DCB: case GST_MPEGTS_STREAM_TYPE_SCTE_DSMCC_DCB:
@ -711,6 +712,15 @@ _stream_is_private_section (GstMpegtsPMTStream * stream)
case GST_MPEGTS_STREAM_TYPE_METADATA_SECTIONS: case GST_MPEGTS_STREAM_TYPE_METADATA_SECTIONS:
/* known PSI streams */ /* known PSI streams */
return TRUE; return TRUE;
case GST_MPEGTS_STREAM_TYPE_SCTE_SIT:
{
guint32 registration_id =
get_registration_from_descriptors (pmt->descriptors);
/* Not a private section stream */
if (registration_id != DRF_ID_CUEI)
return FALSE;
return TRUE;
}
default: default:
return FALSE; return FALSE;
} }
@ -818,7 +828,7 @@ mpegts_base_is_program_update (MpegTSBase * base,
GST_DEBUG GST_DEBUG
("New stream 0x%04x has a different stream type (new:%d, old:%d)", ("New stream 0x%04x has a different stream type (new:%d, old:%d)",
stream->pid, stream->stream_type, oldstream->stream_type); stream->pid, stream->stream_type, oldstream->stream_type);
} else if (!_stream_is_private_section (stream)) { } else if (!_stream_is_private_section (new_pmt, stream)) {
/* FIXME : We should actually be checking a bit deeper, /* FIXME : We should actually be checking a bit deeper,
* especially for private streams (where the differentiation is * especially for private streams (where the differentiation is
* done at the registration level) */ * done at the registration level) */
@ -855,7 +865,7 @@ mpegts_base_deactivate_program (MpegTSBase * base, MpegTSBaseProgram * program)
/* Only unset the is_pes/known_psi bit if the PID isn't used in any other active /* Only unset the is_pes/known_psi bit if the PID isn't used in any other active
* program */ * program */
if (!mpegts_pid_in_active_programs (base, stream->pid)) { if (!mpegts_pid_in_active_programs (base, stream->pid)) {
if (_stream_is_private_section (stream)) { if (_stream_is_private_section (program->pmt, stream)) {
if (base->parse_private_sections) if (base->parse_private_sections)
MPEGTS_BIT_UNSET (base->known_psi, stream->pid); MPEGTS_BIT_UNSET (base->known_psi, stream->pid);
} else { } else {
@ -908,7 +918,7 @@ mpegts_base_activate_program (MpegTSBase * base, MpegTSBaseProgram * program,
for (i = 0; i < pmt->streams->len; ++i) { for (i = 0; i < pmt->streams->len; ++i) {
GstMpegtsPMTStream *stream = g_ptr_array_index (pmt->streams, i); GstMpegtsPMTStream *stream = g_ptr_array_index (pmt->streams, i);
if (_stream_is_private_section (stream)) { if (_stream_is_private_section (pmt, stream)) {
if (base->parse_private_sections) if (base->parse_private_sections)
MPEGTS_BIT_SET (base->known_psi, stream->pid); MPEGTS_BIT_SET (base->known_psi, stream->pid);
} else { } else {

View file

@ -68,6 +68,113 @@ static const guint8 stt_data_check[] = {
0xc0, 0x00, 0xc4, 0x86, 0x56, 0xa5 0xc0, 0x00, 0xc4, 0x86, 0x56, 0xa5
}; };
GST_START_TEST (test_scte_sit)
{
GstMpegtsSCTESIT *sit;
GstMpegtsSection *sit_section;
GstMpegtsSCTESpliceEvent *event;
guint8 *data;
gsize data_size;
/* Try a simple NULL command before anything else */
sit = gst_mpegts_scte_sit_new ();
sit->tier = 123;
sit->pts_adjustment = 0x1fedcba12;
sit->splice_command_type = GST_MTS_SCTE_SPLICE_COMMAND_NULL;
sit_section = gst_mpegts_section_from_scte_sit (sit, 456);
fail_if (sit_section == NULL);
fail_unless (sit_section->short_section);
/* Serialize and check that we can re-parse it into something valid */
data = gst_mpegts_section_packetize (sit_section, &data_size);
fail_if (data == NULL);
GST_MEMDUMP ("section", data, data_size);
GST_LOG ("here");
sit_section->destroy_parsed (sit_section->cached_parsed);
sit_section->cached_parsed = NULL;
sit = (GstMpegtsSCTESIT *) gst_mpegts_section_get_scte_sit (sit_section);
fail_if (sit == NULL);
/* Check the values */
fail_unless (sit->encrypted_packet == FALSE);
fail_unless (sit->pts_adjustment == 0x1fedcba12);
fail_unless (sit->tier == 123);
fail_unless (sit->splice_command_type == GST_MTS_SCTE_SPLICE_COMMAND_NULL);
gst_mpegts_section_unref (sit_section);
/* Same thing but now with an insert command */
sit = gst_mpegts_scte_sit_new ();
sit->tier = 123;
sit->pts_adjustment = 0x1fedcba12;
sit->splice_command_type = GST_MTS_SCTE_SPLICE_COMMAND_INSERT;
event = gst_mpegts_scte_splice_event_new ();
event->insert_event = TRUE;
event->splice_event_id = 4285;
event->program_splice_flag = TRUE;
event->duration_flag = TRUE;
event->splice_immediate_flag = FALSE;
event->program_splice_time_specified = TRUE;
event->program_splice_time = 0x1fdecba12;
event->break_duration_auto_return = TRUE;
event->break_duration = 590000;
event->unique_program_id = 4256;
event->avail_num = 2;
event->avails_expected = 2;
g_ptr_array_add (sit->splices, event);
sit_section = gst_mpegts_section_from_scte_sit (sit, 456);
fail_if (sit_section == NULL);
fail_unless (sit_section->short_section);
/* Serialize and check that we can re-parse it into something valid */
data = gst_mpegts_section_packetize (sit_section, &data_size);
fail_if (data == NULL);
GST_MEMDUMP ("section", data, data_size);
GST_LOG ("here");
sit_section->destroy_parsed (sit_section->cached_parsed);
sit_section->cached_parsed = NULL;
sit = (GstMpegtsSCTESIT *) gst_mpegts_section_get_scte_sit (sit_section);
fail_if (sit == NULL);
/* Check the values */
fail_unless (sit->encrypted_packet == FALSE);
fail_unless (sit->pts_adjustment == 0x1fedcba12);
fail_unless (sit->tier == 123);
fail_unless (sit->pts_adjustment == 0x1fedcba12);
fail_unless (sit->splice_command_type == GST_MTS_SCTE_SPLICE_COMMAND_INSERT);
event = g_ptr_array_index (sit->splices, 0);
fail_unless (event->insert_event == TRUE);
fail_unless (event->splice_event_id == 4285);
fail_unless (event->program_splice_flag == TRUE);
fail_unless (event->duration_flag == TRUE);
fail_unless (event->splice_immediate_flag == FALSE);
fail_unless (event->program_splice_time_specified == TRUE);
fail_unless (event->program_splice_time == 0x1fdecba12);
fail_unless (event->break_duration_auto_return == TRUE);
fail_unless (event->break_duration == 590000);
fail_unless (event->unique_program_id == 4256);
fail_unless (event->avail_num == 2);
fail_unless (event->avails_expected == 2);
gst_mpegts_section_unref (sit_section);
}
GST_END_TEST;
GST_START_TEST (test_mpegts_pat) GST_START_TEST (test_mpegts_pat)
{ {
GstMpegtsPatProgram *program; GstMpegtsPatProgram *program;
@ -570,6 +677,7 @@ mpegts_suite (void)
gst_mpegts_initialize (); gst_mpegts_initialize ();
suite_add_tcase (s, tc_chain); suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_scte_sit);
tcase_add_test (tc_chain, test_mpegts_pat); tcase_add_test (tc_chain, test_mpegts_pat);
tcase_add_test (tc_chain, test_mpegts_pmt); tcase_add_test (tc_chain, test_mpegts_pmt);
tcase_add_test (tc_chain, test_mpegts_nit); tcase_add_test (tc_chain, test_mpegts_nit);

View file

@ -31,6 +31,7 @@
#include <glib/gprintf.h> #include <glib/gprintf.h>
#include <gst/gst.h> #include <gst/gst.h>
#include <gst/mpegts/mpegts.h> #include <gst/mpegts/mpegts.h>
#define MPEGTIME_TO_GSTTIME(t) ((t) * (guint64)100000 / 9)
static void static void
gst_info_dump_mem_line (gchar * linebuf, gsize linebuf_size, gst_info_dump_mem_line (gchar * linebuf, gsize linebuf_size,
@ -130,6 +131,22 @@ table_id_name (gint val)
return en->value_nick; return en->value_nick;
} }
static const gchar *
stream_type_name (gint val)
{
GEnumValue *en;
en = g_enum_get_value (G_ENUM_CLASS (g_type_class_peek
(GST_TYPE_MPEGTS_STREAM_TYPE)), val);
if (en == NULL)
/* Else try with SCTE enum types */
en = g_enum_get_value (G_ENUM_CLASS (g_type_class_peek
(GST_TYPE_MPEGTS_SCTE_STREAM_TYPE)), val);
if (en == NULL)
return "UNKNOWN/PRIVATE";
return en->value_nick;
}
static const gchar * static const gchar *
enum_name (GType instance_type, gint val) enum_name (GType instance_type, gint val)
{ {
@ -798,8 +815,7 @@ dump_pmt (GstMpegtsSection * section)
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
GstMpegtsPMTStream *stream = g_ptr_array_index (pmt->streams, i); GstMpegtsPMTStream *stream = g_ptr_array_index (pmt->streams, i);
g_printf (" pid:0x%04x , stream_type:0x%02x (%s)\n", stream->pid, g_printf (" pid:0x%04x , stream_type:0x%02x (%s)\n", stream->pid,
stream->stream_type, stream->stream_type, stream_type_name (stream->stream_type));
enum_name (GST_TYPE_MPEGTS_STREAM_TYPE, stream->stream_type));
dump_descriptors (stream->descriptors, 9); dump_descriptors (stream->descriptors, 9);
} }
} }
@ -1111,6 +1127,96 @@ dump_cat (GstMpegtsSection * section)
g_ptr_array_unref (descriptors); g_ptr_array_unref (descriptors);
} }
static const gchar *
scte_descriptor_name (guint8 tag)
{
switch (tag) {
case 0x00:
return "avail";
case 0x01:
return "DTMF";
case 0x02:
return "segmentation";
case 0x03:
return "time";
case 0x04:
return "audio";
default:
return "UNKNOWN";
}
}
static void
dump_scte_descriptors (GPtrArray * descriptors, guint spacing)
{
guint i;
for (i = 0; i < descriptors->len; i++) {
GstMpegtsDescriptor *desc = g_ptr_array_index (descriptors, i);
g_printf ("%*s [scte descriptor 0x%02x (%s) length:%d]\n", spacing, "",
desc->tag, scte_descriptor_name (desc->tag), desc->length);
if (DUMP_DESCRIPTORS)
dump_memory_content (desc, spacing + 2);
/* FIXME : Add parsing of SCTE descriptors */
}
}
static void
dump_scte_sit (GstMpegtsSection * section)
{
const GstMpegtsSCTESIT *sit = gst_mpegts_section_get_scte_sit (section);
guint i, len;
g_assert (sit);
g_printf (" encrypted_packet : %d\n", sit->encrypted_packet);
if (sit->encrypted_packet) {
g_printf (" encryption_algorithm: %d\n", sit->encryption_algorithm);
g_printf (" cw_index : %d\n", sit->cw_index);
g_printf (" tier : %d\n", sit->tier);
}
g_printf (" pts_adjustment : %" G_GUINT64_FORMAT " (%"
GST_TIME_FORMAT ")\n", sit->pts_adjustment,
GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (sit->pts_adjustment)));
g_printf (" command_type : %d\n", sit->splice_command_type);
if ((len = sit->splices->len)) {
g_printf (" %d splice(s):\n", len);
for (i = 0; i < len; i++) {
GstMpegtsSCTESpliceEvent *event = g_ptr_array_index (sit->splices, i);
g_printf (" event_id:%d event_cancel_indicator:%d\n",
event->splice_event_id, event->splice_event_cancel_indicator);
if (!event->splice_event_cancel_indicator) {
g_printf (" out_of_network_indicator:%d\n",
event->out_of_network_indicator);
if (event->program_splice_flag) {
if (event->program_splice_time_specified)
g_printf (" program_splice_time:%" G_GUINT64_FORMAT " (%"
GST_TIME_FORMAT ")\n", event->program_splice_time,
GST_TIME_ARGS (MPEGTIME_TO_GSTTIME
(event->program_splice_time)));
else
g_printf (" program_splice_time not specified\n");
}
if (event->duration_flag) {
g_printf (" break_duration_auto_return:%d\n",
event->break_duration_auto_return);
g_printf (" break_duration:%" G_GUINT64_FORMAT " (%"
GST_TIME_FORMAT ")\n", event->break_duration,
GST_TIME_ARGS (MPEGTIME_TO_GSTTIME (event->break_duration)));
}
g_printf (" unique_program_id : %d\n", event->unique_program_id);
g_printf (" avail num/expected : %d/%d\n",
event->avail_num, event->avails_expected);
}
}
}
dump_scte_descriptors (sit->descriptors, 4);
}
static void static void
dump_section (GstMpegtsSection * section) dump_section (GstMpegtsSection * section)
{ {
@ -1158,6 +1264,9 @@ dump_section (GstMpegtsSection * section)
case GST_MPEGTS_SECTION_ATSC_STT: case GST_MPEGTS_SECTION_ATSC_STT:
dump_stt (section); dump_stt (section);
break; break;
case GST_MPEGTS_SECTION_SCTE_SIT:
dump_scte_sit (section);
break;
default: default:
g_printf (" Unknown section type\n"); g_printf (" Unknown section type\n");
break; break;
@ -1251,6 +1360,8 @@ main (int argc, gchar ** argv)
g_type_class_ref (GST_TYPE_MPEGTS_DVB_LINKAGE_HAND_OVER_TYPE); g_type_class_ref (GST_TYPE_MPEGTS_DVB_LINKAGE_HAND_OVER_TYPE);
g_type_class_ref (GST_TYPE_MPEGTS_COMPONENT_STREAM_CONTENT); g_type_class_ref (GST_TYPE_MPEGTS_COMPONENT_STREAM_CONTENT);
g_type_class_ref (GST_TYPE_MPEGTS_CONTENT_NIBBLE_HI); g_type_class_ref (GST_TYPE_MPEGTS_CONTENT_NIBBLE_HI);
g_type_class_ref (GST_TYPE_MPEGTS_SCTE_STREAM_TYPE);
g_type_class_ref (GST_TYPE_MPEGTS_SECTION_SCTE_TABLE_ID);
mainloop = g_main_loop_new (NULL, FALSE); mainloop = g_main_loop_new (NULL, FALSE);