From 20b715ac7970681bd38ce26c4b8f0a076229a88c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 23 Jan 2009 11:50:29 +0100 Subject: Add seeking support to mmssrc. Fixes bug #469930. Add proper seeking support to mmssrc and clean up some code. This requires libmms >= 0.4. --- ext/libmms/gstmms.c | 253 ++++++++++++++++++++++++++++++++++++++++------------ ext/libmms/gstmms.h | 7 +- 2 files changed, 199 insertions(+), 61 deletions(-) (limited to 'ext/libmms') diff --git a/ext/libmms/gstmms.c b/ext/libmms/gstmms.c index 87df7ffc..f04e410f 100644 --- a/ext/libmms/gstmms.c +++ b/ext/libmms/gstmms.c @@ -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 @@ -285,6 +396,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) { @@ -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; } diff --git a/ext/libmms/gstmms.h b/ext/libmms/gstmms.h index 7e7143a4..5410cc15 100644 --- a/ext/libmms/gstmms.h +++ b/ext/libmms/gstmms.h @@ -6,8 +6,7 @@ #define __GST_MMS_H__ #include -#include -#include +#include #include 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 -- cgit v1.2.1