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,25 +218,27 @@ 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);
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; break;
default: default:
res = FALSE; 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 /* 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
mms->connection = NULL; 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
if (mms->connection_h != NULL) { is expensive */
mmsh_close (mms->connection_h); if (mmsx_get_current_pos (mms->connection) >
mms->connection_h = NULL; 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; 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