Add seeking support to mmssrc. Fixes bug #469930.

Add proper seeking support to mmssrc and clean
up some code. This requires libmms >= 0.4.
This commit is contained in:
Hans de Goede 2009-01-23 11:50:29 +01:00 committed by Sebastian Dröge
parent eac03fc77e
commit 20b715ac79
3 changed files with 200 additions and 62 deletions

View file

@ -820,7 +820,7 @@ dnl *** libmms ***
translit(dnm, m, l) AM_CONDITIONAL(USE_LIBMMS, true) translit(dnm, m, l) AM_CONDITIONAL(USE_LIBMMS, true)
AG_GST_CHECK_FEATURE(LIBMMS, [mms protocol library], libmms, [ AG_GST_CHECK_FEATURE(LIBMMS, [mms protocol library], libmms, [
dnl check with pkg-config first 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" HAVE_LIBMMS="no"
AC_MSG_RESULT(no) AC_MSG_RESULT(no)
]) ])

View 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_start (GstBaseSrc * bsrc);
static gboolean gst_mms_stop (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 GstFlowReturn gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf);
static void static void
@ -127,6 +133,11 @@ gst_mms_class_init (GstMMSClass * klass)
gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_mms_create); 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 /* initialize the new element
@ -143,10 +154,9 @@ gst_mms_init (GstMMS * mmssrc, GstMMSClass * g_class)
GST_DEBUG_FUNCPTR (gst_mms_get_query_types)); GST_DEBUG_FUNCPTR (gst_mms_get_query_types));
mmssrc->uri_name = NULL; mmssrc->uri_name = NULL;
mmssrc->current_connection_uri_name = NULL;
mmssrc->connection = NULL; mmssrc->connection = NULL;
mmssrc->connection_h = NULL;
mmssrc->connection_speed = DEFAULT_CONNECTION_SPEED; mmssrc->connection_speed = DEFAULT_CONNECTION_SPEED;
GST_BASE_SRC (mmssrc)->blocksize = 2048;
} }
static void static void
@ -154,6 +164,18 @@ gst_mms_finalize (GObject * gobject)
{ {
GstMMS *mmssrc = GST_MMS (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) { if (mmssrc->uri_name) {
g_free (mmssrc->uri_name); g_free (mmssrc->uri_name);
mmssrc->uri_name = NULL; mmssrc->uri_name = NULL;
@ -196,26 +218,28 @@ gst_mms_src_query (GstPad * pad, GstQuery * query)
res = FALSE; res = FALSE;
break; break;
} }
if (mmssrc->connection) { value = (gint64) mmsx_get_current_pos (mmssrc->connection);
value = (gint64) mms_get_current_pos (mmssrc->connection);
} else {
value = (gint64) mmsh_get_current_pos (mmssrc->connection_h);
}
gst_query_set_position (query, format, value); gst_query_set_position (query, format, value);
break; break;
case GST_QUERY_DURATION: case GST_QUERY_DURATION:
gst_query_parse_duration (query, &format, &value); if (!mmsx_get_seekable (mmssrc->connection)) {
if (format != GST_FORMAT_BYTES) {
res = FALSE; res = FALSE;
break; break;
} }
if (mmssrc->connection) { gst_query_parse_duration (query, &format, &value);
value = (gint64) mms_get_length (mmssrc->connection); switch (format) {
} else { case GST_FORMAT_BYTES:
value = (gint64) mmsh_get_length (mmssrc->connection_h); value = (gint64) mmsx_get_length (mmssrc->connection);
}
gst_query_set_duration (query, format, value); gst_query_set_duration (query, format, value);
break; 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;
}
break;
default: default:
res = FALSE; res = FALSE;
break; break;
@ -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 /* get function
* this function generates new data when needed * this function generates new data when needed
*/ */
@ -238,35 +345,39 @@ gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf)
guint8 *data; guint8 *data;
guint blocksize; guint blocksize;
gint result; gint result;
mms_off_t offset;
*buf = NULL;
mmssrc = GST_MMS (psrc); mmssrc = GST_MMS (psrc);
GST_OBJECT_LOCK (mmssrc); offset = mmsx_get_current_pos (mmssrc->connection);
blocksize = GST_BASE_SRC (mmssrc)->blocksize;
GST_OBJECT_UNLOCK (mmssrc); /* 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); *buf = gst_buffer_new_and_alloc (blocksize);
data = GST_BUFFER_DATA (*buf); data = GST_BUFFER_DATA (*buf);
GST_BUFFER_SIZE (*buf) = 0; GST_BUFFER_SIZE (*buf) = 0;
GST_LOG_OBJECT (mmssrc, "reading %d bytes", blocksize); GST_LOG_OBJECT (mmssrc, "reading %d bytes", blocksize);
if (mmssrc->connection) { result = mmsx_read (NULL, mmssrc->connection, (char *) data, blocksize);
result = mms_read (NULL, mmssrc->connection, (char *) data, blocksize);
} else {
result = mmsh_read (NULL, mmssrc->connection_h, (char *) data, blocksize);
}
/* EOS? */ /* EOS? */
if (result == 0) if (result == 0)
goto eos; goto eos;
if (mmssrc->connection) { GST_BUFFER_OFFSET (*buf) = offset;
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_SIZE (*buf) = result; GST_BUFFER_SIZE (*buf) = result;
GST_LOG_OBJECT (mmssrc, "Returning buffer with offset %" G_GINT64_FORMAT 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 static gboolean
gst_mms_start (GstBaseSrc * bsrc) gst_mms_start (GstBaseSrc * bsrc)
{ {
@ -301,27 +434,35 @@ gst_mms_start (GstBaseSrc * bsrc)
else else
bandwidth_avail = G_MAXINT; 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 */ /* FIXME: pass some sane arguments here */
GST_DEBUG_OBJECT (mms, GST_DEBUG_OBJECT (mms,
"Trying mms_connect (%s) with bandwidth constraint of %d bps", "Trying mms_connect (%s) with bandwidth constraint of %d bps",
mms->uri_name, bandwidth_avail); mms->uri_name, bandwidth_avail);
mms->connection = mms_connect (NULL, NULL, mms->uri_name, bandwidth_avail); mms->connection = mmsx_connect (NULL, NULL, mms->uri_name, bandwidth_avail);
if (mms->connection) if (mms->connection) {
goto success; /* Save the uri name so that it can be checked for connection reusing,
see above. */
GST_DEBUG_OBJECT (mms, mms->current_connection_uri_name = g_strdup (mms->uri_name);
"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:
{
GST_DEBUG_OBJECT (mms, "Connect successful"); GST_DEBUG_OBJECT (mms, "Connect successful");
return TRUE; return TRUE;
} else {
GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
("Could not connect to this stream"), (NULL));
return FALSE;
} }
no_uri: no_uri:
@ -330,13 +471,6 @@ no_uri:
("No URI to open specified"), (NULL)); ("No URI to open specified"), (NULL));
return FALSE; return FALSE;
} }
no_connect:
{
GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
("Could not connect to this stream"), (NULL));
return FALSE;
}
} }
static gboolean static gboolean
@ -346,12 +480,17 @@ gst_mms_stop (GstBaseSrc * bsrc)
mms = GST_MMS (bsrc); mms = GST_MMS (bsrc);
if (mms->connection != NULL) { if (mms->connection != NULL) {
mms_close (mms->connection); /* 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; mms->connection = NULL;
g_free (mms->current_connection_uri_name);
mms->current_connection_uri_name = NULL;
} }
if (mms->connection_h != NULL) {
mmsh_close (mms->connection_h);
mms->connection_h = NULL;
} }
return TRUE; return TRUE;
} }

View file

@ -6,8 +6,7 @@
#define __GST_MMS_H__ #define __GST_MMS_H__
#include <gst/gst.h> #include <gst/gst.h>
#include <libmms/mms.h> #include <libmms/mmsx.h>
#include <libmms/mmsh.h>
#include <gst/base/gstpushsrc.h> #include <gst/base/gstpushsrc.h>
G_BEGIN_DECLS G_BEGIN_DECLS
@ -32,10 +31,10 @@ struct _GstMMS
GstPushSrc parent; GstPushSrc parent;
gchar *uri_name; gchar *uri_name;
gchar *current_connection_uri_name;
guint connection_speed; guint connection_speed;
mms_t *connection; mmsx_t *connection;
mmsh_t *connection_h;
}; };
struct _GstMMSClass struct _GstMMSClass