Add seeking support to mmssrc. Fixes bug #469930.
authorHans de Goede <jwrdegoede@fedoraproject.org>
Fri, 23 Jan 2009 10:50:29 +0000 (11:50 +0100)
committerSebastian Dröge <sebastian.droege@collabora.co.uk>
Fri, 23 Jan 2009 10:50:29 +0000 (11:50 +0100)
Add proper seeking support to mmssrc and clean
up some code. This requires libmms >= 0.4.

configure.ac
ext/libmms/gstmms.c
ext/libmms/gstmms.h

index 167953b..ec8e9a5 100644 (file)
@@ -820,7 +820,7 @@ dnl *** libmms ***
 translit(dnm, m, l) AM_CONDITIONAL(USE_LIBMMS, true)
 AG_GST_CHECK_FEATURE(LIBMMS, [mms protocol library], libmms, [
   dnl check with pkg-config first
-  PKG_CHECK_MODULES(LIBMMS, libmms >= 0.2, HAVE_LIBMMS="yes", [
+  PKG_CHECK_MODULES(LIBMMS, libmms >= 0.4, HAVE_LIBMMS="yes", [
     HAVE_LIBMMS="no"
     AC_MSG_RESULT(no)
   ])
index 87df7ff..f04e410 100644 (file)
@@ -65,6 +65,12 @@ static gboolean gst_mms_src_query (GstPad * pad, GstQuery * query);
 
 static gboolean gst_mms_start (GstBaseSrc * bsrc);
 static gboolean gst_mms_stop (GstBaseSrc * bsrc);
+static gboolean gst_mms_is_seekable (GstBaseSrc * src);
+static gboolean gst_mms_get_size (GstBaseSrc * src, guint64 * size);
+static gboolean gst_mms_prepare_seek_segment (GstBaseSrc * src,
+    GstEvent * event, GstSegment * segment);
+static gboolean gst_mms_do_seek (GstBaseSrc * src, GstSegment * segment);
+
 static GstFlowReturn gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf);
 
 static void
@@ -127,6 +133,11 @@ gst_mms_class_init (GstMMSClass * klass)
 
   gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_mms_create);
 
+  gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_mms_is_seekable);
+  gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_mms_get_size);
+  gstbasesrc_class->prepare_seek_segment =
+      GST_DEBUG_FUNCPTR (gst_mms_prepare_seek_segment);
+  gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_mms_do_seek);
 }
 
 /* initialize the new element
@@ -143,10 +154,9 @@ gst_mms_init (GstMMS * mmssrc, GstMMSClass * g_class)
       GST_DEBUG_FUNCPTR (gst_mms_get_query_types));
 
   mmssrc->uri_name = NULL;
+  mmssrc->current_connection_uri_name = NULL;
   mmssrc->connection = NULL;
-  mmssrc->connection_h = NULL;
   mmssrc->connection_speed = DEFAULT_CONNECTION_SPEED;
-  GST_BASE_SRC (mmssrc)->blocksize = 2048;
 }
 
 static void
@@ -154,6 +164,18 @@ gst_mms_finalize (GObject * gobject)
 {
   GstMMS *mmssrc = GST_MMS (gobject);
 
+  /* We may still have a connection open, as we preserve unused / pristine
+     open connections in stop to reuse them in start. */
+  if (mmssrc->connection) {
+    mmsx_close (mmssrc->connection);
+    mmssrc->connection = NULL;
+  }
+
+  if (mmssrc->current_connection_uri_name) {
+    g_free (mmssrc->current_connection_uri_name);
+    mmssrc->current_connection_uri_name = NULL;
+  }
+
   if (mmssrc->uri_name) {
     g_free (mmssrc->uri_name);
     mmssrc->uri_name = NULL;
@@ -196,25 +218,27 @@ gst_mms_src_query (GstPad * pad, GstQuery * query)
         res = FALSE;
         break;
       }
-      if (mmssrc->connection) {
-        value = (gint64) mms_get_current_pos (mmssrc->connection);
-      } else {
-        value = (gint64) mmsh_get_current_pos (mmssrc->connection_h);
-      }
+      value = (gint64) mmsx_get_current_pos (mmssrc->connection);
       gst_query_set_position (query, format, value);
       break;
     case GST_QUERY_DURATION:
-      gst_query_parse_duration (query, &format, &value);
-      if (format != GST_FORMAT_BYTES) {
+      if (!mmsx_get_seekable (mmssrc->connection)) {
         res = FALSE;
         break;
       }
-      if (mmssrc->connection) {
-        value = (gint64) mms_get_length (mmssrc->connection);
-      } else {
-        value = (gint64) mmsh_get_length (mmssrc->connection_h);
+      gst_query_parse_duration (query, &format, &value);
+      switch (format) {
+        case GST_FORMAT_BYTES:
+          value = (gint64) mmsx_get_length (mmssrc->connection);
+          gst_query_set_duration (query, format, value);
+          break;
+        case GST_FORMAT_TIME:
+          value = mmsx_get_time_length (mmssrc->connection) * GST_SECOND;
+          gst_query_set_duration (query, format, value);
+          break;
+        default:
+          res = FALSE;
       }
-      gst_query_set_duration (query, format, value);
       break;
     default:
       res = FALSE;
@@ -226,6 +250,89 @@ gst_mms_src_query (GstPad * pad, GstQuery * query)
 
 }
 
+
+static gboolean
+gst_mms_prepare_seek_segment (GstBaseSrc * src, GstEvent * event,
+    GstSegment * segment)
+{
+  GstSeekType cur_type, stop_type;
+  gint64 cur, stop;
+  GstSeekFlags flags;
+  GstFormat seek_format;
+  gdouble rate;
+  GstMMS *mmssrc = GST_MMS (src);
+
+  gst_event_parse_seek (event, &rate, &seek_format, &flags,
+      &cur_type, &cur, &stop_type, &stop);
+
+  if (seek_format != GST_FORMAT_BYTES && seek_format != GST_FORMAT_TIME) {
+    GST_LOG_OBJECT (mmssrc, "Only byte or time seeking is supported");
+    return FALSE;
+  }
+
+  if (stop_type != GST_SEEK_TYPE_NONE) {
+    GST_LOG_OBJECT (mmssrc, "Stop seeking not supported");
+    return FALSE;
+  }
+
+  if (cur_type != GST_SEEK_TYPE_NONE && cur_type != GST_SEEK_TYPE_SET) {
+    GST_LOG_OBJECT (mmssrc, "Only absolute seeking is supported");
+    return FALSE;
+  }
+
+  /* We would like to convert from GST_FORMAT_TIME to GST_FORMAT_BYTES here
+     when needed, but we cannot as to do that we need to actually do the seek,
+     so we handle this in do_seek instead. */
+
+  /* FIXME implement relative seeking, we could do any needed relevant
+     seeking calculations here (in seek_format metrics), before the re-init
+     of the segment. */
+
+  gst_segment_init (segment, seek_format);
+  gst_segment_set_seek (segment, rate, seek_format, flags, cur_type, cur,
+      stop_type, stop, NULL);
+
+  return TRUE;
+}
+
+static gboolean
+gst_mms_do_seek (GstBaseSrc * src, GstSegment * segment)
+{
+  mms_off_t start;
+  GstMMS *mmssrc = GST_MMS (src);
+
+  if (segment->format == GST_FORMAT_TIME) {
+    if (!mmsx_time_seek (NULL, mmssrc->connection,
+            (double) segment->start / GST_SECOND)) {
+      GST_LOG_OBJECT (mmssrc, "mmsx_time_seek() failed");
+      return FALSE;
+    }
+    start = mmsx_get_current_pos (mmssrc->connection);
+    GST_LOG_OBJECT (mmssrc, "sought to %f sec, offset after seek: %lld\n",
+        (double) segment->start / GST_SECOND, start);
+  } else if (segment->format == GST_FORMAT_BYTES) {
+    start = mmsx_seek (NULL, mmssrc->connection, segment->start, SEEK_SET);
+    /* mmsx_seek will close and reopen the connection when seeking with the
+       mmsh protocol, if the reopening fails this is indicated with -1 */
+    if (start == -1) {
+      GST_DEBUG_OBJECT (mmssrc, "connection broken during seek");
+      return FALSE;
+    }
+    GST_DEBUG_OBJECT (mmssrc, "sought to: %llu bytes, result: %lld",
+        segment->start, start);
+  } else {
+    GST_DEBUG_OBJECT (mmssrc, "unsupported seek segment format: %d",
+        (int) segment->format);
+    return FALSE;
+  }
+  gst_segment_init (segment, GST_FORMAT_BYTES);
+  gst_segment_set_seek (segment, segment->rate, GST_FORMAT_BYTES,
+      segment->flags, GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_NONE,
+      segment->stop, NULL);
+  return TRUE;
+}
+
+
 /* get function
  * this function generates new data when needed
  */
@@ -238,35 +345,39 @@ gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf)
   guint8 *data;
   guint blocksize;
   gint result;
+  mms_off_t offset;
+
+  *buf = NULL;
 
   mmssrc = GST_MMS (psrc);
 
-  GST_OBJECT_LOCK (mmssrc);
-  blocksize = GST_BASE_SRC (mmssrc)->blocksize;
-  GST_OBJECT_UNLOCK (mmssrc);
+  offset = mmsx_get_current_pos (mmssrc->connection);
+
+  /* Check if a seek perhaps has wrecked our connection */
+  if (offset == -1) {
+    GST_DEBUG_OBJECT (mmssrc,
+        "connection broken (probably an error during mmsx_seek_time during a convert query) returning FLOW_ERROR");
+    return GST_FLOW_ERROR;
+  }
+
+  /* Choose blocksize best for optimum performance */
+  if (offset == 0)
+    blocksize = mmsx_get_asf_header_len (mmssrc->connection);
+  else
+    blocksize = mmsx_get_asf_packet_len (mmssrc->connection);
 
   *buf = gst_buffer_new_and_alloc (blocksize);
 
   data = GST_BUFFER_DATA (*buf);
   GST_BUFFER_SIZE (*buf) = 0;
   GST_LOG_OBJECT (mmssrc, "reading %d bytes", blocksize);
-  if (mmssrc->connection) {
-    result = mms_read (NULL, mmssrc->connection, (char *) data, blocksize);
-  } else {
-    result = mmsh_read (NULL, mmssrc->connection_h, (char *) data, blocksize);
-  }
+  result = mmsx_read (NULL, mmssrc->connection, (char *) data, blocksize);
 
   /* EOS? */
   if (result == 0)
     goto eos;
 
-  if (mmssrc->connection) {
-    GST_BUFFER_OFFSET (*buf) =
-        mms_get_current_pos (mmssrc->connection) - result;
-  } else {
-    GST_BUFFER_OFFSET (*buf) =
-        mmsh_get_current_pos (mmssrc->connection_h) - result;
-  }
+  GST_BUFFER_OFFSET (*buf) = offset;
   GST_BUFFER_SIZE (*buf) = result;
 
   GST_LOG_OBJECT (mmssrc, "Returning buffer with offset %" G_GINT64_FORMAT
@@ -286,6 +397,28 @@ eos:
 }
 
 static gboolean
+gst_mms_is_seekable (GstBaseSrc * src)
+{
+  GstMMS *mmssrc = GST_MMS (src);
+
+  return mmsx_get_seekable (mmssrc->connection);
+}
+
+static gboolean
+gst_mms_get_size (GstBaseSrc * src, guint64 * size)
+{
+  GstMMS *mmssrc = GST_MMS (src);
+
+  /* non seekable usually means live streams, and get_length() returns,
+     erm, interesting values for live streams */
+  if (!mmsx_get_seekable (mmssrc->connection))
+    return FALSE;
+
+  *size = mmsx_get_length (mmssrc->connection);
+  return TRUE;
+}
+
+static gboolean
 gst_mms_start (GstBaseSrc * bsrc)
 {
   GstMMS *mms;
@@ -301,40 +434,41 @@ gst_mms_start (GstBaseSrc * bsrc)
   else
     bandwidth_avail = G_MAXINT;
 
+  /* If we already have a connection, and the uri isn't changed, reuse it,
+     as connecting is expensive. */
+  if (mms->connection) {
+    if (!strcmp (mms->uri_name, mms->current_connection_uri_name)) {
+      GST_DEBUG_OBJECT (mms, "Reusing existing connection for %s",
+          mms->uri_name);
+      return TRUE;
+    } else {
+      mmsx_close (mms->connection);
+      g_free (mms->current_connection_uri_name);
+      mms->current_connection_uri_name = NULL;
+    }
+  }
+
   /* FIXME: pass some sane arguments here */
   GST_DEBUG_OBJECT (mms,
       "Trying mms_connect (%s) with bandwidth constraint of %d bps",
       mms->uri_name, bandwidth_avail);
-  mms->connection = mms_connect (NULL, NULL, mms->uri_name, bandwidth_avail);
-  if (mms->connection)
-    goto success;
-
-  GST_DEBUG_OBJECT (mms,
-      "Trying mmsh_connect (%s) with bandwidth constraint of %d bps",
-      mms->uri_name, bandwidth_avail);
-  mms->connection_h = mmsh_connect (NULL, NULL, mms->uri_name, bandwidth_avail);
-  if (!mms->connection_h)
-    goto no_connect;
-
-  /* fall through */
-
-success:
-  {
+  mms->connection = mmsx_connect (NULL, NULL, mms->uri_name, bandwidth_avail);
+  if (mms->connection) {
+    /* Save the uri name so that it can be checked for connection reusing,
+       see above. */
+    mms->current_connection_uri_name = g_strdup (mms->uri_name);
     GST_DEBUG_OBJECT (mms, "Connect successful");
     return TRUE;
-  }
-
-no_uri:
-  {
+  } else {
     GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
-        ("No URI to open specified"), (NULL));
+        ("Could not connect to this stream"), (NULL));
     return FALSE;
   }
 
-no_connect:
+no_uri:
   {
     GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
-        ("Could not connect to this stream"), (NULL));
+        ("No URI to open specified"), (NULL));
     return FALSE;
   }
 }
@@ -346,12 +480,17 @@ gst_mms_stop (GstBaseSrc * bsrc)
 
   mms = GST_MMS (bsrc);
   if (mms->connection != NULL) {
-    mms_close (mms->connection);
-    mms->connection = NULL;
-  }
-  if (mms->connection_h != NULL) {
-    mmsh_close (mms->connection_h);
-    mms->connection_h = NULL;
+    /* Check if the connection is still pristine, that is if no more then
+       just the mmslib cached asf header has been read. If it is still pristine
+       preserve it as we often are re-started with the same URL and connecting
+       is expensive */
+    if (mmsx_get_current_pos (mms->connection) >
+        mmsx_get_asf_header_len (mms->connection)) {
+      mmsx_close (mms->connection);
+      mms->connection = NULL;
+      g_free (mms->current_connection_uri_name);
+      mms->current_connection_uri_name = NULL;
+    }
   }
   return TRUE;
 }
index 7e7143a..5410cc1 100644 (file)
@@ -6,8 +6,7 @@
 #define __GST_MMS_H__
 
 #include <gst/gst.h>
-#include <libmms/mms.h>
-#include <libmms/mmsh.h>
+#include <libmms/mmsx.h>
 #include <gst/base/gstpushsrc.h>
 
 G_BEGIN_DECLS
@@ -32,10 +31,10 @@ struct _GstMMS
   GstPushSrc parent;
 
   gchar  *uri_name;
+  gchar  *current_connection_uri_name;
   guint  connection_speed;
   
-  mms_t  *connection;
-  mmsh_t *connection_h;
+  mmsx_t *connection;
 };
 
 struct _GstMMSClass