mpegts: Add support for SCTE-35 sections
authorEdward Hervey <edward@centricular.com>
Thu, 26 Sep 2019 15:28:27 +0000 (17:28 +0200)
committerEdward Hervey <bilboed@bilboed.com>
Thu, 31 Oct 2019 12:31:27 +0000 (12:31 +0000)
Not all commands are supported, but the most common ones are.
Both parsing and packetizing is supported

gst-libs/gst/mpegts/gst-scte-section.c [new file with mode: 0644]
gst-libs/gst/mpegts/gst-scte-section.h
gst-libs/gst/mpegts/gstmpegtssection.c
gst-libs/gst/mpegts/gstmpegtssection.h
gst-libs/gst/mpegts/meson.build
gst/mpegtsdemux/mpegtsbase.c
tests/check/libs/mpegts.c
tests/examples/mpegts/ts-parser.c

diff --git a/gst-libs/gst/mpegts/gst-scte-section.c b/gst-libs/gst/mpegts/gst-scte-section.c
new file mode 100644 (file)
index 0000000..39a5b60
--- /dev/null
@@ -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;
+}
index 34c43cd..8a753c7 100644 (file)
@@ -34,6 +34,7 @@ G_BEGIN_DECLS
  * 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
@@ -51,7 +52,9 @@ typedef enum {
   /* 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 */
@@ -97,6 +100,117 @@ typedef enum {
 
 } 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 */
index 68a5da5..f5077a9 100644 (file)
@@ -1126,6 +1126,9 @@ _identify_section (guint16 pid, guint8 table_id)
       if (pid == 0x1ffb)
         return GST_MPEGTS_SECTION_ATSC_RRT;
       break;
+    case GST_MTS_TABLE_ID_SCTE_SPLICE:
+      return GST_MPEGTS_SECTION_SCTE_SIT;
+      break;
     default:
       /* Handle ranges */
       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_CAT:
     case GST_MPEGTS_SECTION_TSDT:
+    case GST_MPEGTS_SECTION_SCTE_SIT:
       /* Tables from ISO/IEC 13818-1 has a '0' bit
        * 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);
       break;
     default:
index aeb6121..b254183 100644 (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_EIT: ATSC Event Information 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.
  */
@@ -79,7 +80,8 @@ typedef enum {
   GST_MPEGTS_SECTION_ATSC_ETT,
   GST_MPEGTS_SECTION_ATSC_EIT,
   GST_MPEGTS_SECTION_ATSC_STT,
-  GST_MPEGTS_SECTION_ATSC_RRT
+  GST_MPEGTS_SECTION_ATSC_RRT,
+  GST_MPEGTS_SECTION_SCTE_SIT
 } GstMpegtsSectionType;
 
 /**
index fc6a263..7fdb14c 100644 (file)
@@ -4,6 +4,7 @@ mpegts_sources = [
   'gst-dvb-descriptor.c',
   'gst-dvb-section.c',
   'gst-atsc-section.c',
+  'gst-scte-section.c',
 ]
 
 mpegts_headers = [
index 759b705..d49ab5e 100644 (file)
@@ -688,7 +688,8 @@ mpegts_base_update_program (MpegTSBase * base, MpegTSBaseProgram * program,
 
 
 static gboolean
-_stream_is_private_section (GstMpegtsPMTStream * stream)
+_stream_is_private_section (const GstMpegtsPMT * pmt,
+    GstMpegtsPMTStream * stream)
 {
   switch (stream->stream_type) {
     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:
       /* known PSI streams */
       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:
       return FALSE;
   }
@@ -818,7 +828,7 @@ mpegts_base_is_program_update (MpegTSBase * base,
       GST_DEBUG
           ("New stream 0x%04x has a different stream type (new:%d, old:%d)",
           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,
        * especially for private streams (where the differentiation is
        * 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
        * program */
       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)
             MPEGTS_BIT_UNSET (base->known_psi, stream->pid);
         } else {
@@ -908,7 +918,7 @@ mpegts_base_activate_program (MpegTSBase * base, MpegTSBaseProgram * program,
 
   for (i = 0; i < pmt->streams->len; ++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)
         MPEGTS_BIT_SET (base->known_psi, stream->pid);
     } else {
index a1ce6e8..2198768 100644 (file)
@@ -68,6 +68,113 @@ static const guint8 stt_data_check[] = {
   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;
@@ -570,6 +677,7 @@ mpegts_suite (void)
   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);
index 3cfb90c..cf339f8 100644 (file)
@@ -31,6 +31,7 @@
 #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,
@@ -131,6 +132,22 @@ table_id_name (gint val)
 }
 
 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;
@@ -798,8 +815,7 @@ dump_pmt (GstMpegtsSection * section)
   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);
   }
 }
@@ -1111,6 +1127,96 @@ dump_cat (GstMpegtsSection * section)
   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)
 {
@@ -1158,6 +1264,9 @@ 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;
@@ -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_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);