--- /dev/null
+/*
+ * 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;
+}
* GstMpegtsScteStreamType:
* @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_SIT: SCTE-35 Splice Information Table
* @GST_MPEGTS_STREAM_TYPE_SCTE_DST_NRT: SCTE-07 Data Service or
* Network Resource Table
* @GST_MPEGTS_STREAM_TYPE_SCTE_DSMCC_DCB: Type B - DSM-CC Data Carousel
/* 0x01 - 0x82 : defined in other specs */
GST_MPEGTS_STREAM_TYPE_SCTE_SUBTITLING = 0x82, /* Subtitling 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 */
/* 0x96 - 0xaf : defined in other specs */
GST_MPEGTS_STREAM_TYPE_SCTE_DSMCC_DCB = 0xb0, /* Data Carousel Type B */
} 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
#endif /* GST_SCTE_SECTION_H */
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)
{
GstMpegtsPatProgram *program;
gst_mpegts_initialize ();
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_pmt);
tcase_add_test (tc_chain, test_mpegts_nit);
#include <glib/gprintf.h>
#include <gst/gst.h>
#include <gst/mpegts/mpegts.h>
+#define MPEGTIME_TO_GSTTIME(t) ((t) * (guint64)100000 / 9)
static void
gst_info_dump_mem_line (gchar * linebuf, gsize linebuf_size,
}
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 *
enum_name (GType instance_type, gint val)
{
GEnumValue *en;
for (i = 0; i < len; i++) {
GstMpegtsPMTStream *stream = g_ptr_array_index (pmt->streams, i);
g_printf (" pid:0x%04x , stream_type:0x%02x (%s)\n", stream->pid,
- stream->stream_type,
- enum_name (GST_TYPE_MPEGTS_STREAM_TYPE, stream->stream_type));
+ stream->stream_type, stream_type_name (stream->stream_type));
dump_descriptors (stream->descriptors, 9);
}
}
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
dump_section (GstMpegtsSection * section)
{
case GST_MPEGTS_SECTION_ATSC_STT:
dump_stt (section);
break;
+ case GST_MPEGTS_SECTION_SCTE_SIT:
+ dump_scte_sit (section);
+ break;
default:
g_printf (" Unknown section type\n");
break;
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_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);