summaryrefslogtreecommitdiff
path: root/subprojects/gst-plugins-bad/ext/libmms/gstmms.c
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/gst-plugins-bad/ext/libmms/gstmms.c')
-rw-r--r--subprojects/gst-plugins-bad/ext/libmms/gstmms.c637
1 files changed, 637 insertions, 0 deletions
diff --git a/subprojects/gst-plugins-bad/ext/libmms/gstmms.c b/subprojects/gst-plugins-bad/ext/libmms/gstmms.c
new file mode 100644
index 0000000000..b127a1f57a
--- /dev/null
+++ b/subprojects/gst-plugins-bad/ext/libmms/gstmms.c
@@ -0,0 +1,637 @@
+/*
+ *
+ * GStreamer
+ * Copyright (C) 1999-2001 Erik Walthinsen <omega@cse.ogi.edu>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <gst/gst.h>
+#include <stdio.h>
+#include <string.h>
+#include "gstmms.h"
+
+#define DEFAULT_CONNECTION_SPEED 0
+
+enum
+{
+ PROP_0,
+ PROP_LOCATION,
+ PROP_CONNECTION_SPEED
+};
+
+
+GST_DEBUG_CATEGORY_STATIC (mmssrc_debug);
+#define GST_CAT_DEFAULT mmssrc_debug
+
+static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS ("video/x-ms-asf")
+ );
+
+static void gst_mms_finalize (GObject * gobject);
+static void gst_mms_uri_handler_init (gpointer g_iface, gpointer iface_data);
+
+static void gst_mms_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_mms_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static gboolean gst_mms_query (GstBaseSrc * src, 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 gboolean gst_mms_uri_set_uri (GstURIHandler * handler,
+ const gchar * uri, GError ** error);
+
+#define gst_mms_parent_class parent_class
+G_DEFINE_TYPE_WITH_CODE (GstMMS, gst_mms, GST_TYPE_PUSH_SRC,
+ G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_mms_uri_handler_init));
+GST_ELEMENT_REGISTER_DEFINE (mmssrc, "mmssrc", GST_RANK_NONE, GST_TYPE_MMS);
+
+/* initialize the plugin's class */
+static void
+gst_mms_class_init (GstMMSClass * klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+ GstElementClass *gstelement_class = (GstElementClass *) klass;
+ GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass;
+ GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass;
+
+ gobject_class->set_property = gst_mms_set_property;
+ gobject_class->get_property = gst_mms_get_property;
+ gobject_class->finalize = gst_mms_finalize;
+
+ g_object_class_install_property (gobject_class, PROP_LOCATION,
+ g_param_spec_string ("location", "location",
+ "Host URL to connect to. Accepted are mms://, mmsu://, mmst:// URL types",
+ NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /* Note: connection-speed is intentionaly limited to G_MAXINT as libmms
+ * uses int for it */
+ g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED,
+ g_param_spec_uint64 ("connection-speed", "Connection Speed",
+ "Network connection speed in kbps (0 = unknown)",
+ 0, G_MAXINT / 1000, DEFAULT_CONNECTION_SPEED,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
+
+ gst_element_class_set_static_metadata (gstelement_class,
+ "MMS streaming source", "Source/Network",
+ "Receive data streamed via MSFT Multi Media Server protocol",
+ "Maciej Katafiasz <mathrick@users.sourceforge.net>");
+
+ GST_DEBUG_CATEGORY_INIT (mmssrc_debug, "mmssrc", 0, "MMS Source Element");
+
+ gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_mms_start);
+ gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_mms_stop);
+
+ 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);
+ gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_mms_query);
+}
+
+/* initialize the new element
+ * instantiate pads and add them to element
+ * set functions
+ * initialize structure
+ */
+static void
+gst_mms_init (GstMMS * mmssrc)
+{
+ mmssrc->uri_name = NULL;
+ mmssrc->current_connection_uri_name = NULL;
+ mmssrc->connection = NULL;
+ mmssrc->connection_speed = DEFAULT_CONNECTION_SPEED;
+}
+
+static void
+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;
+ }
+
+ G_OBJECT_CLASS (parent_class)->finalize (gobject);
+}
+
+/* FIXME operating in TIME rather than BYTES could remove this altogether
+ * and be more convenient elsewhere */
+static gboolean
+gst_mms_query (GstBaseSrc * src, GstQuery * query)
+{
+ GstMMS *mmssrc = GST_MMS (src);
+ gboolean res = TRUE;
+ GstFormat format;
+ gint64 value;
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_POSITION:
+ gst_query_parse_position (query, &format, &value);
+ if (format != GST_FORMAT_BYTES) {
+ res = FALSE;
+ break;
+ }
+ value = (gint64) mmsx_get_current_pos (mmssrc->connection);
+ gst_query_set_position (query, format, value);
+ break;
+ case GST_QUERY_DURATION:
+ if (!mmsx_get_seekable (mmssrc->connection)) {
+ res = FALSE;
+ break;
+ }
+ 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;
+ }
+ break;
+ default:
+ /* chain to parent */
+ res =
+ GST_BASE_SRC_CLASS (parent_class)->query (GST_BASE_SRC (src), query);
+ break;
+ }
+
+ return res;
+}
+
+
+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;
+
+ 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 (src, "Only byte or time seeking is supported");
+ return FALSE;
+ }
+
+ if (stop_type != GST_SEEK_TYPE_NONE) {
+ GST_LOG_OBJECT (src, "Stop seeking not supported");
+ return FALSE;
+ }
+
+ if (cur_type != GST_SEEK_TYPE_NONE && cur_type != GST_SEEK_TYPE_SET) {
+ GST_LOG_OBJECT (src, "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_do_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)
+{
+ gint64 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_INFO_OBJECT (mmssrc,
+ "performed seek to %" GST_TIME_FORMAT ", offset after " "seek: %"
+ G_GINT64_FORMAT, GST_TIME_ARGS (segment->start), 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_INFO_OBJECT (mmssrc, "performed seek to: %" G_GINT64_FORMAT " bytes, "
+ "result: %" G_GINT64_FORMAT, segment->start, start);
+ } else {
+ GST_DEBUG_OBJECT (mmssrc, "unsupported seek segment format: %s",
+ GST_STR_NULL (gst_format_get_name (segment->format)));
+ return FALSE;
+ }
+ gst_segment_init (segment, GST_FORMAT_BYTES);
+ gst_segment_do_seek (segment, segment->rate, GST_FORMAT_BYTES,
+ GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_NONE,
+ segment->stop, NULL);
+ return TRUE;
+}
+
+
+/* get function
+ * this function generates new data when needed
+ */
+
+
+static GstFlowReturn
+gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf)
+{
+ GstMMS *mmssrc = GST_MMS (psrc);
+ guint8 *data;
+ guint blocksize;
+ gint result;
+ goffset offset;
+
+ *buf = NULL;
+
+ offset = mmsx_get_current_pos (mmssrc->connection);
+
+ /* Check if a seek perhaps has wrecked our connection */
+ if (offset == -1) {
+ GST_ERROR_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);
+
+ data = g_try_malloc (blocksize);
+ if (!data) {
+ GST_ERROR_OBJECT (mmssrc, "Failed to allocate %u bytes", blocksize);
+ return GST_FLOW_ERROR;
+ }
+
+ GST_LOG_OBJECT (mmssrc, "reading %d bytes", blocksize);
+ result = mmsx_read (NULL, mmssrc->connection, (char *) data, blocksize);
+ /* EOS? */
+ if (result == 0)
+ goto eos;
+
+ *buf = gst_buffer_new_wrapped (data, result);
+ GST_BUFFER_OFFSET (*buf) = offset;
+
+ GST_LOG_OBJECT (mmssrc, "Returning buffer with offset %" G_GOFFSET_FORMAT
+ " and size %u", offset, result);
+
+ return GST_FLOW_OK;
+
+eos:
+ {
+ GST_DEBUG_OBJECT (mmssrc, "EOS");
+ g_free (data);
+ *buf = NULL;
+ return GST_FLOW_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 = GST_MMS (bsrc);
+ guint bandwidth_avail;
+
+ if (!mms->uri_name || *mms->uri_name == '\0')
+ goto no_uri;
+
+ if (mms->connection_speed)
+ bandwidth_avail = mms->connection_speed;
+ 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 = 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;
+ } else {
+ gchar *url, *location;
+
+ GST_ERROR_OBJECT (mms,
+ "Could not connect to this stream, redirecting to rtsp");
+ location = strstr (mms->uri_name, "://");
+ if (location == NULL || *location == '\0' || *(location + 3) == '\0')
+ goto no_uri;
+ url = g_strdup_printf ("rtsp://%s", location + 3);
+
+ gst_element_post_message (GST_ELEMENT_CAST (mms),
+ gst_message_new_element (GST_OBJECT_CAST (mms),
+ gst_structure_new ("redirect", "new-location", G_TYPE_STRING, url,
+ NULL)));
+
+ /* post an error message as well, so that applications that don't handle
+ * redirect messages get to see a proper error message */
+ GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
+ ("Could not connect to streaming server."),
+ ("A redirect message was posted on the bus and should have been "
+ "handled by the application."));
+
+ return FALSE;
+ }
+
+no_uri:
+ {
+ GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
+ ("No URI to open specified"), (NULL));
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_mms_stop (GstBaseSrc * bsrc)
+{
+ GstMMS *mms = GST_MMS (bsrc);
+
+ if (mms->connection != 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;
+}
+
+static void
+gst_mms_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstMMS *mmssrc = GST_MMS (object);
+
+ switch (prop_id) {
+ case PROP_LOCATION:
+ gst_mms_uri_set_uri (GST_URI_HANDLER (mmssrc),
+ g_value_get_string (value), NULL);
+ break;
+ case PROP_CONNECTION_SPEED:
+ GST_OBJECT_LOCK (mmssrc);
+ mmssrc->connection_speed = g_value_get_uint64 (value) * 1000;
+ GST_OBJECT_UNLOCK (mmssrc);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_mms_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstMMS *mmssrc = GST_MMS (object);
+
+ GST_OBJECT_LOCK (mmssrc);
+ switch (prop_id) {
+ case PROP_LOCATION:
+ if (mmssrc->uri_name)
+ g_value_set_string (value, mmssrc->uri_name);
+ break;
+ case PROP_CONNECTION_SPEED:
+ g_value_set_uint64 (value, mmssrc->connection_speed / 1000);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+ GST_OBJECT_UNLOCK (mmssrc);
+}
+
+/* entry point to initialize the plug-in
+ * initialize the plug-in itself
+ * register the element factories and pad templates
+ * register the features
+ */
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ return GST_ELEMENT_REGISTER (mmssrc, plugin);
+}
+
+static GstURIType
+gst_mms_uri_get_type (GType type)
+{
+ return GST_URI_SRC;
+}
+
+static const gchar *const *
+gst_mms_uri_get_protocols (GType type)
+{
+ static const gchar *protocols[] = { "mms", "mmsh", "mmst", "mmsu", NULL };
+
+ return protocols;
+}
+
+static gchar *
+gst_mms_uri_get_uri (GstURIHandler * handler)
+{
+ GstMMS *src = GST_MMS (handler);
+
+ /* FIXME: make thread-safe */
+ return g_strdup (src->uri_name);
+}
+
+static gchar *
+gst_mms_src_make_valid_uri (const gchar * uri)
+{
+ gchar *protocol;
+ const gchar *colon, *tmp;
+ gsize len;
+
+ if (!uri || !gst_uri_is_valid (uri))
+ return NULL;
+
+ protocol = gst_uri_get_protocol (uri);
+
+ if ((strcmp (protocol, "mms") != 0) && (strcmp (protocol, "mmsh") != 0) &&
+ (strcmp (protocol, "mmst") != 0) && (strcmp (protocol, "mmsu") != 0)) {
+ g_free (protocol);
+ return FALSE;
+ }
+ g_free (protocol);
+
+ colon = strstr (uri, "://");
+ if (!colon)
+ return NULL;
+
+ tmp = colon + 3;
+ len = strlen (tmp);
+ if (len == 0)
+ return NULL;
+
+ /* libmms segfaults if there's no hostname or
+ * no / after the hostname
+ */
+ colon = strstr (tmp, "/");
+ if (colon == tmp)
+ return NULL;
+
+ if (strstr (tmp, "/") == NULL) {
+ gchar *ret;
+
+ len = strlen (uri);
+ ret = g_malloc0 (len + 2);
+ memcpy (ret, uri, len);
+ ret[len] = '/';
+ return ret;
+ } else {
+ return g_strdup (uri);
+ }
+}
+
+static gboolean
+gst_mms_uri_set_uri (GstURIHandler * handler, const gchar * uri,
+ GError ** error)
+{
+ GstMMS *src = GST_MMS (handler);
+ gchar *fixed_uri;
+
+ fixed_uri = gst_mms_src_make_valid_uri (uri);
+ if (!fixed_uri && uri) {
+ g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
+ "Invalid MMS URI");
+ return FALSE;
+ }
+
+ GST_OBJECT_LOCK (src);
+ g_free (src->uri_name);
+ src->uri_name = fixed_uri;
+ GST_OBJECT_UNLOCK (src);
+
+ return TRUE;
+}
+
+static void
+gst_mms_uri_handler_init (gpointer g_iface, gpointer iface_data)
+{
+ GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
+
+ iface->get_type = gst_mms_uri_get_type;
+ iface->get_protocols = gst_mms_uri_get_protocols;
+ iface->get_uri = gst_mms_uri_get_uri;
+ iface->set_uri = gst_mms_uri_set_uri;
+}
+
+/* this is the structure that gst-register looks for
+ * so keep the name plugin_desc, or you cannot get your plug-in registered */
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ mms,
+ "Microsoft Multi Media Server streaming protocol support",
+ plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)