mpegtsmux: Add support for muxing SI tables
authorJesper Larsen <jesper.larsen@ixonos.com>
Wed, 20 Nov 2013 10:14:46 +0000 (11:14 +0100)
committerJesper Larsen <jesper.larsen@ixonos.com>
Thu, 6 Feb 2014 14:55:46 +0000 (15:55 +0100)
The muxer is now able to include DVB sections in the transport stream.

The si-interval property will determine how often the SI tables are
muxed into the stream.

The section is handled by the mpeg-ts library. Below is a small example
that will include a Netork Information Table with a Network Name
descriptor in the stream.

GstMpegTsNIT *nit;
GstMpegTsDescriptor *descriptor;
GstMpegTsSection *section;
GstElement *mpegtsmux;

gst_mpegts_initialize ();

nit = gst_mpegts_section_nit_new ();
nit->actual_network = TRUE;

descriptor = gst_mpegts_descriptor_from_dvb_network_name ("Network name");
g_ptr_array_add (nit->descriptors, descriptor);

section = gst_mpegts_section_from_nit (nit);

// mpegtsmux should be retrieved from the pipeline
gst_mpegts_section_send_event (section, mpegtsmux);
gst_mpegts_section_unref (section);

gst/mpegtsmux/Makefile.am
gst/mpegtsmux/mpegtsmux.c
gst/mpegtsmux/mpegtsmux.h
gst/mpegtsmux/tsmux/Makefile.am
gst/mpegtsmux/tsmux/tsmux.c
gst/mpegtsmux/tsmux/tsmux.h
gst/mpegtsmux/tsmux/tsmuxcommon.h

index 8dd002c..8e3cdba 100644 (file)
@@ -7,7 +7,8 @@ libgstmpegtsmux_la_SOURCES = \
        mpegtsmux_aac.c \
        mpegtsmux_ttxt.c
 
-libgstmpegtsmux_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS)
+libgstmpegtsmux_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) \
+                           $(GST_BASE_CFLAGS) $(GST_CFLAGS)
 libgstmpegtsmux_la_LIBADD = $(top_builddir)/gst/mpegtsmux/tsmux/libtsmux.la \
        -lgsttag-@GST_API_VERSION@ \
        $(GST_PLUGINS_BASE_LIBS) -lgstvideo-@GST_API_VERSION@ $(GST_BASE_LIBS) $(GST_LIBS)
index 848833f..da57ff4 100644 (file)
@@ -94,6 +94,7 @@
 
 #include <gst/tag/tag.h>
 #include <gst/video/video.h>
+#include <gst/mpegts/mpegts.h>
 
 #include "mpegtsmux.h"
 
@@ -110,7 +111,8 @@ enum
   ARG_M2TS_MODE,
   ARG_PAT_INTERVAL,
   ARG_PMT_INTERVAL,
-  ARG_ALIGNMENT
+  ARG_ALIGNMENT,
+  ARG_SI_INTERVAL
 };
 
 #define MPEGTSMUX_DEFAULT_ALIGNMENT    -1
@@ -177,6 +179,7 @@ static GstPad *mpegtsmux_request_new_pad (GstElement * element,
 static void mpegtsmux_release_pad (GstElement * element, GstPad * pad);
 static GstStateChangeReturn mpegtsmux_change_state (GstElement * element,
     GstStateChange transition);
+static gboolean mpegtsmux_send_event (GstElement * element, GstEvent * event);
 static void mpegtsdemux_set_header_on_caps (MpegTsMux * mux);
 static gboolean mpegtsmux_src_event (GstPad * pad, GstObject * parent,
     GstEvent * event);
@@ -242,6 +245,7 @@ mpegtsmux_class_init (MpegTsMuxClass * klass)
   gstelement_class->request_new_pad = mpegtsmux_request_new_pad;
   gstelement_class->release_pad = mpegtsmux_release_pad;
   gstelement_class->change_state = mpegtsmux_change_state;
+  gstelement_class->send_event = mpegtsmux_send_event;
 
 #if 0
   gstelement_class->set_index = GST_DEBUG_FUNCPTR (mpegtsmux_set_index);
@@ -277,6 +281,12 @@ mpegtsmux_class_init (MpegTsMuxClass * klass)
           "(-1 = auto, 0 = all available packets)",
           -1, G_MAXINT, MPEGTSMUX_DEFAULT_ALIGNMENT,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_SI_INTERVAL,
+      g_param_spec_uint ("si-interval", "SI interval",
+          "Set the interval (in ticks of the 90kHz clock) for writing out the Service"
+          "Information tables", 1, G_MAXUINT, TSMUX_DEFAULT_SI_INTERVAL,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 }
 
 static void
@@ -310,6 +320,7 @@ mpegtsmux_init (MpegTsMux * mux)
   mux->m2ts_mode = MPEGTSMUX_DEFAULT_M2TS;
   mux->pat_interval = TSMUX_DEFAULT_PAT_INTERVAL;
   mux->pmt_interval = TSMUX_DEFAULT_PMT_INTERVAL;
+  mux->si_interval = TSMUX_DEFAULT_SI_INTERVAL;
   mux->prog_map = NULL;
   mux->alignment = MPEGTSMUX_DEFAULT_ALIGNMENT;
 
@@ -478,6 +489,10 @@ gst_mpegtsmux_set_property (GObject * object, guint prop_id,
     case ARG_ALIGNMENT:
       mux->alignment = g_value_get_int (value);
       break;
+    case ARG_SI_INTERVAL:
+      mux->si_interval = g_value_get_uint (value);
+      tsmux_set_si_interval (mux->tsmux, mux->si_interval);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -506,6 +521,9 @@ gst_mpegtsmux_get_property (GObject * object, guint prop_id,
     case ARG_ALIGNMENT:
       g_value_set_int (value, mux->alignment);
       break;
+    case ARG_SI_INTERVAL:
+      g_value_set_uint (value, mux->si_interval);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -1707,6 +1725,29 @@ mpegtsmux_change_state (GstElement * element, GstStateChange transition)
 }
 
 static gboolean
+mpegtsmux_send_event (GstElement * element, GstEvent * event)
+{
+  GstMpegTsSection *section;
+  MpegTsMux *mux = GST_MPEG_TSMUX (element);
+
+  g_return_val_if_fail (event != NULL, FALSE);
+
+  section = gst_event_parse_mpegts_section (event);
+  gst_event_unref (event);
+
+  if (section) {
+    GST_DEBUG ("Received event with mpegts section");
+
+    /* TODO: Check that the section type is supported */
+    tsmux_add_mpegts_si_section (mux->tsmux, section);
+
+    return TRUE;
+  }
+
+  return FALSE;
+}
+
+static gboolean
 plugin_init (GstPlugin * plugin)
 {
   if (!gst_element_register (plugin, "mpegtsmux", GST_RANK_PRIMARY,
index c894e58..f47707f 100644 (file)
@@ -140,6 +140,7 @@ struct MpegTsMux {
   guint pat_interval;
   guint pmt_interval;
   gint alignment;
+  guint si_interval;
 
   /* state */
   gboolean first;
index e995cf7..29e52a0 100644 (file)
@@ -1,7 +1,8 @@
 noinst_LTLIBRARIES = libtsmux.la
 
-libtsmux_la_CFLAGS = $(GST_CFLAGS)
-libtsmux_la_LIBADD = $(GST_LIBS)
+libtsmux_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) $(GST_CFLAGS)
+libtsmux_la_LIBADD = $(GST_LIBS) \
+       $(top_builddir)/gst-libs/gst/mpegts/libgstmpegts-$(GST_API_VERSION).la
 libtsmux_la_LDFLAGS = -module -avoid-version
 libtsmux_la_SOURCES = tsmux.c tsmuxstream.c
 
index 7835940..e9b88dd 100644 (file)
@@ -83,6 +83,8 @@
 
 #include <string.h>
 
+#include <gst/mpegts/mpegts.h>
+
 #include "tsmux.h"
 #include "tsmuxstream.h"
 #include "crc.h"
 
 static gboolean tsmux_write_pat (TsMux * mux);
 static gboolean tsmux_write_pmt (TsMux * mux, TsMuxProgram * program);
+static void
+tsmux_section_free (TsMuxSection * section)
+{
+  gst_mpegts_section_unref (section->section);
+}
 
 /**
  * tsmux_new:
@@ -139,6 +146,13 @@ tsmux_new (void)
   mux->last_pat_ts = -1;
   mux->pat_interval = TSMUX_DEFAULT_PAT_INTERVAL;
 
+  mux->si_changed = TRUE;
+  mux->last_si_ts = -1;
+  mux->si_interval = TSMUX_DEFAULT_SI_INTERVAL;
+
+  mux->si_sections = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+      NULL, (GDestroyNotify) tsmux_section_free);
+
   return mux;
 }
 
@@ -215,6 +229,72 @@ tsmux_get_pat_interval (TsMux * mux)
 }
 
 /**
+ * tsmux_set_si_interval:
+ * @mux: a #TsMux
+ * @freq: a new SI table interval
+ *
+ * Set the interval (in cycles of the 90kHz clock) for writing out the SI tables.
+ *
+ */
+void
+tsmux_set_si_interval (TsMux * mux, guint freq)
+{
+  g_return_if_fail (mux != NULL);
+
+  mux->si_interval = freq;
+}
+
+/**
+ * tsmux_get_si_interval:
+ * @mux: a #TsMux
+ *
+ * Get the configured SI table interval. See also tsmux_set_si_interval().
+ *
+ * Returns: the configured SI interval
+ */
+guint
+tsmux_get_si_interval (TsMux * mux)
+{
+  g_return_val_if_fail (mux != NULL, 0);
+
+  return mux->si_interval;
+}
+
+/**
+ * tsmux_add_mpegts_si_section:
+ * @mux: a #TsMux
+ * @section: (transfer full): a #GstMpegTsSection to add
+ *
+ * Add a Service Information #GstMpegTsSection to the stream
+ *
+ * Returns: #TRUE on success, #FALSE otherwise
+ */
+gboolean
+tsmux_add_mpegts_si_section (TsMux * mux, GstMpegTsSection * section)
+{
+  TsMuxSection *tsmux_section;
+
+  g_return_val_if_fail (mux != NULL, FALSE);
+  g_return_val_if_fail (section != NULL, FALSE);
+  g_return_val_if_fail (mux->si_sections != NULL, FALSE);
+
+  tsmux_section = g_slice_new0 (TsMuxSection);
+
+  GST_DEBUG ("Adding mpegts section with type %d to mux",
+      section->section_type);
+
+  tsmux_section->section = section;
+  tsmux_section->pi.pid = section->pid;
+
+  g_hash_table_insert (mux->si_sections,
+      GINT_TO_POINTER (section->section_type), tsmux_section);
+
+  mux->si_changed = TRUE;
+
+  return TRUE;
+}
+
+/**
  * tsmux_free:
  * @mux: a #TsMux
  *
@@ -244,6 +324,9 @@ tsmux_free (TsMux * mux)
   }
   g_list_free (mux->streams);
 
+  /* Free SI table sections */
+  g_hash_table_destroy (mux->si_sections);
+
   g_slice_free (TsMux, mux);
 }
 
@@ -754,6 +837,115 @@ tsmux_write_ts_header (guint8 * buf, TsMuxPacketInfo * pi,
   return TRUE;
 }
 
+static gboolean
+tsmux_section_write_packet (GstMpegTsSectionType * type,
+    TsMuxSection * section, TsMux * mux)
+{
+  GstBuffer *section_buffer;
+  GstBuffer *packet_buffer = NULL;
+  GstMemory *mem;
+  guint8 *packet;
+  guint8 *data;
+  gsize data_size;
+  gsize payload_written;
+  guint len = 0, offset = 0, payload_len = 0;
+
+  g_return_val_if_fail (section != NULL, FALSE);
+  g_return_val_if_fail (mux != NULL, FALSE);
+
+  /* Mark the start of new PES unit */
+  section->pi.packet_start_unit_indicator = TRUE;
+
+  data = gst_mpegts_section_packetize (section->section, &data_size);
+
+  if (!data) {
+    TS_DEBUG ("Could not packetize section");
+    return FALSE;
+  }
+
+  /* Mark payload data size */
+  section->pi.stream_avail = data_size;
+  payload_written = 0;
+
+  section_buffer = gst_buffer_new_wrapped (data, data_size);
+
+  TS_DEBUG ("Section buffer with size %" G_GSIZE_FORMAT " created",
+      gst_buffer_get_size (section_buffer));
+
+  while (section->pi.stream_avail > 0) {
+
+    packet = g_malloc (TSMUX_PACKET_LENGTH);
+
+    if (section->pi.packet_start_unit_indicator) {
+      /* Wee need room for a pointer byte */
+      section->pi.stream_avail++;
+
+      if (!tsmux_write_ts_header (packet, &section->pi, &len, &offset))
+        goto fail;
+
+      /* Write the pointer byte */
+      packet[offset++] = 0x00;
+      payload_len = len - 1;
+
+    } else {
+      if (!tsmux_write_ts_header (packet, &section->pi, &len, &offset))
+        goto fail;
+      payload_len = len;
+    }
+
+    /* Wrap the TS header and adaption field in a GstMemory */
+    mem = gst_memory_new_wrapped (GST_MEMORY_FLAG_READONLY,
+        packet, TSMUX_PACKET_LENGTH, 0, offset, packet, g_free);
+
+    TS_DEBUG ("Creating packet buffer at offset "
+        "%" G_GSIZE_FORMAT " with length %u", payload_written, payload_len);
+
+    packet_buffer = gst_buffer_copy_region (section_buffer, GST_BUFFER_COPY_ALL,
+        payload_written, payload_len);
+
+    /* Prepend the header to the section data */
+    gst_buffer_prepend_memory (packet_buffer, mem);
+
+    TS_DEBUG ("Writing %d bytes to section. %d bytes remaining",
+        len, section->pi.stream_avail - len);
+
+    /* Push the packet without PCR */
+    if G_UNLIKELY
+      (!tsmux_packet_out (mux, packet_buffer, -1)) {
+      /* Buffer given away */
+      packet_buffer = NULL;
+      goto fail;
+      }
+
+    section->pi.stream_avail -= len;
+    payload_written += payload_len;
+    section->pi.packet_start_unit_indicator = FALSE;
+  }
+
+  return TRUE;
+
+fail:
+  if (packet)
+    g_free (packet);
+  if (packet_buffer)
+    gst_buffer_unref (packet_buffer);
+  if (section_buffer)
+    gst_buffer_unref (section_buffer);
+  return FALSE;
+}
+
+static gboolean
+tsmux_write_si (TsMux * mux)
+{
+  g_hash_table_foreach (mux->si_sections,
+      (GHFunc) tsmux_section_write_packet, mux);
+
+  mux->si_changed = FALSE;
+
+  return TRUE;
+
+}
+
 /**
  * tsmux_write_stream_packet:
  * @mux: a #TsMux
@@ -779,6 +971,7 @@ tsmux_write_stream_packet (TsMux * mux, TsMuxStream * stream)
   if (tsmux_stream_is_pcr (stream)) {
     gint64 cur_pts = tsmux_stream_get_pts (stream);
     gboolean write_pat;
+    gboolean write_si;
     GList *cur;
 
     cur_pcr = 0;
@@ -822,6 +1015,20 @@ tsmux_write_stream_packet (TsMux * mux, TsMuxStream * stream)
         return FALSE;
     }
 
+    /* check if we need to rewrite sit */
+    if (mux->last_si_ts == -1 || mux->si_changed)
+      write_si = TRUE;
+    else if (cur_pts >= mux->last_si_ts + mux->si_interval)
+      write_si = TRUE;
+    else
+      write_si = FALSE;
+
+    if (write_si) {
+      mux->last_si_ts = cur_pts;
+      if (!tsmux_write_si (mux))
+        return FALSE;
+    }
+
     /* check if we need to rewrite any of the current pmts */
     for (cur = mux->programs; cur; cur = cur->next) {
       TsMuxProgram *program = (TsMuxProgram *) cur->data;
index acebcb5..93eba33 100644 (file)
@@ -82,6 +82,8 @@
 
 #include <glib.h>
 
+#include <gst/mpegts/mpegts.h>
+
 #include "tsmuxcommon.h"
 #include "tsmuxstream.h"
 
@@ -104,6 +106,7 @@ typedef void (*TsMuxAllocFunc) (GstBuffer ** buf, void *user_data);
 
 struct TsMuxSection {
   TsMuxPacketInfo pi;
+  GstMpegTsSection *section;
 
   /* Private sections can be up to 4096 bytes */
   guint8 data[TSMUX_MAX_SECTION_LENGTH];
@@ -148,6 +151,9 @@ struct TsMux {
   guint16 next_pmt_pid;
   guint16 next_stream_pid;
 
+  /* Table with TsMuxSection to write */
+  GHashTable *si_sections;
+
   TsMuxSection pat;
   /* PAT transport_stream_id */
   guint16 transport_id;
@@ -160,6 +166,13 @@ struct TsMux {
   /* last time PAT written in MPEG PTS clock time */
   gint64   last_pat_ts;
 
+  /* trigger writing Service Information Tables */
+  gboolean si_changed;
+  /* interval between SIT in MPEG PTS clock time */
+  guint    si_interval;
+  /* last time SIT written in MPEG PTS clock time */
+  gint64   last_si_ts;
+
   /* callback to write finished packet */
   TsMuxWriteFunc write_func;
   void *write_func_data;
@@ -188,6 +201,11 @@ void               tsmux_program_free              (TsMuxProgram *program);
 void           tsmux_set_pmt_interval          (TsMuxProgram *program, guint interval);
 guint          tsmux_get_pmt_interval          (TsMuxProgram *program);
 
+/* SI table management */
+void            tsmux_set_si_interval           (TsMux *mux, guint interval);
+guint           tsmux_get_si_interval           (TsMux *mux);
+gboolean        tsmux_add_mpegts_si_section     (TsMux * mux, GstMpegTsSection * section);
+
 /* stream management */
 TsMuxStream *  tsmux_create_stream             (TsMux *mux, TsMuxStreamType stream_type, guint16 pid, gchar *language);
 TsMuxStream *  tsmux_find_stream               (TsMux *mux, guint16 pid);
index 1accc18..6d5e87f 100644 (file)
@@ -119,6 +119,8 @@ G_BEGIN_DECLS
 #define TSMUX_DEFAULT_PAT_INTERVAL (TSMUX_CLOCK_FREQ / 10)
 /* PMT interval (1/10th sec) */
 #define TSMUX_DEFAULT_PMT_INTERVAL (TSMUX_CLOCK_FREQ / 10)
+/* SI  interval (1/10th sec) */
+#define TSMUX_DEFAULT_SI_INTERVAL  (TSMUX_CLOCK_FREQ / 10)
 
 typedef struct TsMuxPacketInfo TsMuxPacketInfo;
 typedef struct TsMuxProgram TsMuxProgram;