basetsmux: extend SCTE 35 support
authorMathieu Duponchelle <mathieu@centricular.com>
Mon, 5 Apr 2021 22:58:33 +0000 (00:58 +0200)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Sat, 25 Sep 2021 01:29:37 +0000 (01:29 +0000)
Makes it possible to support passing SCTE 35 cue points from
demuxer to muxer, while preserving correct timing.

This will also improve ex nihilo cue points injection, as splice
times and durations are now interpreted as running time values,
and may trigger key unit requests.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/913>

subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmux.c
subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmux.h

index 55d80aa..c01ae16 100644 (file)
@@ -194,6 +194,7 @@ enum
 #define CLOCK_BASE 9LL
 #define CLOCK_FREQ (CLOCK_BASE * 10000) /* 90 kHz PTS clock */
 #define CLOCK_FREQ_SCR (CLOCK_FREQ * 300)       /* 27 MHz SCR clock */
+#define TS_MUX_CLOCK_BASE (TSMUX_CLOCK_FREQ * 10 * 360)
 
 #define GSTTIME_TO_MPEGTIME(time) \
     (((time) > 0 ? (gint64) 1 : (gint64) -1) * \
@@ -356,6 +357,8 @@ gst_base_ts_mux_reset (GstBaseTsMux * mux, gboolean alloc)
   if (si_sections)
     g_hash_table_unref (si_sections);
 
+  mux->last_scte35_event_seqnum = GST_SEQNUM_INVALID;
+
   if (klass->reset)
     klass->reset (mux);
 }
@@ -1245,7 +1248,9 @@ gst_base_ts_mux_aggregate_buffer (GstBaseTsMux * mux,
   if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_PTS (buf))) {
     pts = GSTTIME_TO_MPEGTIME (GST_BUFFER_PTS (buf));
     GST_DEBUG_OBJECT (mux, "Buffer has PTS  %" GST_TIME_FORMAT " pts %"
-        G_GINT64_FORMAT, GST_TIME_ARGS (GST_BUFFER_PTS (buf)), pts);
+        G_GINT64_FORMAT "%s", GST_TIME_ARGS (GST_BUFFER_PTS (buf)), pts,
+        !GST_BUFFER_FLAG_IS_SET (buf,
+            GST_BUFFER_FLAG_DELTA_UNIT) ? " (keyframe)" : "");
   }
 
   if (GST_CLOCK_STIME_IS_VALID (best->dts)) {
@@ -1415,6 +1420,181 @@ gst_base_ts_mux_release_pad (GstElement * element, GstPad * pad)
   GST_ELEMENT_CLASS (parent_class)->release_pad (element, pad);
 }
 
+static GstMpegtsSCTESpliceEvent *
+copy_splice (GstMpegtsSCTESpliceEvent * splice)
+{
+  return g_boxed_copy (GST_TYPE_MPEGTS_SCTE_SPLICE_EVENT, splice);
+}
+
+static GstMpegtsSCTESIT *
+deep_copy_sit (const GstMpegtsSCTESIT * sit)
+{
+  GstMpegtsSCTESIT *sit_copy = g_boxed_copy (GST_TYPE_MPEGTS_SCTE_SIT, sit);
+  GPtrArray *splices_copy =
+      g_ptr_array_copy (sit_copy->splices, (GCopyFunc) copy_splice, NULL);
+
+  g_ptr_array_unref (sit_copy->splices);
+  sit_copy->splices = splices_copy;
+
+  return sit_copy;
+}
+
+/* GstAggregator implementation */
+
+static void
+request_keyframe (GstBaseTsMux * mux, GstClockTime running_time)
+{
+  GList *l;
+  GST_OBJECT_LOCK (mux);
+
+  for (l = GST_ELEMENT_CAST (mux)->sinkpads; l; l = l->next) {
+    gst_pad_push_event (GST_PAD (l->data),
+        gst_video_event_new_upstream_force_key_unit (running_time, TRUE, 0));
+  }
+
+  GST_OBJECT_UNLOCK (mux);
+}
+
+/* Takes ownership of @section */
+static void
+handle_scte35_section (GstBaseTsMux * mux, GstMpegtsSection * section)
+{
+  const GstMpegtsSCTESIT *sit;
+  GstMpegtsSCTESIT *sit_copy;
+  guint i;
+  gboolean forward = TRUE;
+
+  sit = gst_mpegts_section_get_scte_sit (section);
+  sit_copy = deep_copy_sit (sit);
+
+  switch (sit_copy->splice_command_type) {
+    case GST_MTS_SCTE_SPLICE_COMMAND_NULL:
+      /* We implement heartbeating ourselves */
+      forward = FALSE;
+      break;
+    case GST_MTS_SCTE_SPLICE_COMMAND_SCHEDULE:
+      /* Only translate timestamps and forward, splice_insert
+       * messages will precede the future splice points and we
+       * can request keyframes then.
+       */
+      for (i = 0; i < sit_copy->splices->len; i++) {
+        GstMpegtsSCTESpliceEvent *sevent =
+            g_ptr_array_index (sit_copy->splices, i);
+        if (sevent->program_splice_time_specified) {
+          sevent->program_splice_time =
+              GSTTIME_TO_MPEGTIME (sevent->program_splice_time) +
+              TS_MUX_CLOCK_BASE;
+        }
+        if (sevent->duration_flag) {
+          sevent->break_duration = GSTTIME_TO_MPEGTIME (sevent->break_duration);
+        }
+      }
+      break;
+    case GST_MTS_SCTE_SPLICE_COMMAND_INSERT:
+      /* We want keyframes at splice points */
+      for (i = 0; i < sit_copy->splices->len; i++) {
+        guint64 running_time = GST_CLOCK_TIME_NONE;
+
+        GstMpegtsSCTESpliceEvent *sevent =
+            g_ptr_array_index (sit_copy->splices, i);
+        if (sevent->program_splice_time_specified) {
+          GST_DEBUG_OBJECT (mux,
+              "Requesting keyframe for splice point at %" GST_TIME_FORMAT,
+              GST_TIME_ARGS (sevent->program_splice_time));
+          running_time = sevent->program_splice_time;
+          request_keyframe (mux, running_time);
+          sevent->program_splice_time =
+              GSTTIME_TO_MPEGTIME (sevent->program_splice_time) +
+              TS_MUX_CLOCK_BASE;
+        } else {
+          GST_DEBUG_OBJECT (mux,
+              "Requesting keyframe for immediate splice point");
+          request_keyframe (mux, GST_CLOCK_TIME_NONE);
+        }
+
+        if (sevent->duration_flag) {
+          /* Even if auto_return is FALSE, when a break_duration is specified it
+           * is intended as a redundancy mechanism in case the follow-up
+           * splice insert goes missing.
+           *
+           * Schedule a keyframe at that point (if we can calculate its position
+           * accurately).
+           */
+          if (GST_CLOCK_STIME_IS_VALID (running_time)) {
+            GST_DEBUG_OBJECT (mux,
+                "Requesting keyframe for end of break at %" GST_TIME_FORMAT,
+                GST_TIME_ARGS (running_time + sevent->break_duration));
+            request_keyframe (mux, running_time + sevent->break_duration);
+          }
+          sevent->break_duration = GSTTIME_TO_MPEGTIME (sevent->break_duration);
+        }
+      }
+      break;
+    case GST_MTS_SCTE_SPLICE_COMMAND_TIME:{
+      /* Adjust timestamps and potentially request keyframes */
+      gboolean do_request_keyframes = FALSE;
+
+      /* TODO: we can probably be a little more fine-tuned about determining
+       * whether a keyframe is actually needed, but this at least takes care
+       * of the requirement in 10.3.4 that a keyframe should not be created
+       * when the signal contains only a time_descriptor.
+       */
+      for (i = 0; i < sit_copy->descriptors->len; i++) {
+        GstMpegtsDescriptor *descriptor =
+            g_ptr_array_index (sit_copy->descriptors, i);
+
+        switch (descriptor->tag) {
+          case GST_MTS_SCTE_DESC_AVAIL:
+          case GST_MTS_SCTE_DESC_DTMF:
+          case GST_MTS_SCTE_DESC_SEGMENTATION:
+            do_request_keyframes = TRUE;
+            break;
+          case GST_MTS_SCTE_DESC_TIME:
+          case GST_MTS_SCTE_DESC_AUDIO:
+            break;
+        }
+
+        if (do_request_keyframes)
+          break;
+      }
+
+      if (sit_copy->splice_time_specified) {
+        if (do_request_keyframes) {
+          GST_DEBUG_OBJECT (mux,
+              "Requesting keyframe for time signal at %" GST_TIME_FORMAT,
+              GST_TIME_ARGS (sit_copy->splice_time));
+          request_keyframe (mux, sit_copy->splice_time);
+        }
+        sit_copy->splice_time =
+            GSTTIME_TO_MPEGTIME (sit_copy->splice_time) + TS_MUX_CLOCK_BASE;
+      } else if (do_request_keyframes) {
+        GST_DEBUG_OBJECT (mux, "Requesting keyframe for immediate time signal");
+        request_keyframe (mux, GST_CLOCK_TIME_NONE);
+      }
+      break;
+    }
+    case GST_MTS_SCTE_SPLICE_COMMAND_BANDWIDTH:
+    case GST_MTS_SCTE_SPLICE_COMMAND_PRIVATE:
+      /* Just let those go through untouched, none of our business */
+      break;
+    default:
+      break;
+  }
+
+  if (!forward) {
+    gst_mpegts_section_unref (section);
+    return;
+  }
+
+  GST_OBJECT_LOCK (mux);
+  GST_DEBUG_OBJECT (mux, "Storing SCTE section");
+  if (mux->pending_scte35_section)
+    gst_mpegts_section_unref (mux->pending_scte35_section);
+  mux->pending_scte35_section =
+      gst_mpegts_section_from_scte_sit (sit_copy, mux->scte35_pid);
+  GST_OBJECT_UNLOCK (mux);
+}
+
 static gboolean
 gst_base_ts_mux_send_event (GstElement * element, GstEvent * event)
 {
@@ -1427,13 +1607,7 @@ gst_base_ts_mux_send_event (GstElement * element, GstEvent * event)
     GST_DEBUG ("Received event with mpegts section");
 
     if (section->section_type == GST_MPEGTS_SECTION_SCTE_SIT) {
-      /* Will be sent from the streaming threads */
-      GST_DEBUG_OBJECT (mux, "Storing SCTE event");
-      GST_OBJECT_LOCK (element);
-      if (mux->pending_scte35_section)
-        gst_mpegts_section_unref (mux->pending_scte35_section);
-      mux->pending_scte35_section = section;
-      GST_OBJECT_UNLOCK (element);
+      handle_scte35_section (mux, section);
     } else {
       /* TODO: Check that the section type is supported */
       tsmux_add_mpegts_si_section (mux->tsmux, section);
@@ -1465,6 +1639,38 @@ gst_base_ts_mux_sink_event (GstAggregator * agg, GstAggregatorPad * agg_pad,
       GstClockTime timestamp, stream_time, running_time;
       gboolean all_headers;
       guint count;
+      const GstStructure *s;
+
+      s = gst_event_get_structure (event);
+
+      if (gst_structure_has_name (s, "scte-sit") && mux->scte35_pid != 0) {
+
+        /* When operating downstream of tsdemux, tsdemux will send out events
+         * on all its source pads for each splice table it encounters. If we
+         * are remuxing multiple streams it has demuxed, this means we could
+         * unnecessarily repeat the same table multiple times, we avoid that
+         * by deduplicating thanks to the event sequm
+         */
+        if (gst_event_get_seqnum (event) != mux->last_scte35_event_seqnum) {
+          GstMpegtsSection *section;
+
+          gst_structure_get (s, "section", GST_TYPE_MPEGTS_SECTION, &section,
+              NULL);
+
+          if (section) {
+            handle_scte35_section (mux, section);
+            mux->last_scte35_event_seqnum = gst_event_get_seqnum (event);
+          } else {
+            GST_WARNING_OBJECT (ts_pad,
+                "Ignoring scte-sit event without a section");
+          }
+        } else {
+          GST_DEBUG_OBJECT (ts_pad, "Ignoring duplicate scte-sit event");
+        }
+        res = TRUE;
+        forward = FALSE;
+        goto out;
+      }
 
       if (!gst_video_event_is_force_key_unit (event))
         goto out;
index 59114ba..017b048 100644 (file)
@@ -160,6 +160,7 @@ struct GstBaseTsMux {
   guint pcr_interval;
   guint scte35_pid;
   guint scte35_null_interval;
+  guint32 last_scte35_event_seqnum;
 
   /* state */
   gboolean first;