4 * Copyright (C) 1999-2001 Erik Walthinsen <omega@cse.ogi.edu>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
31 #define DEFAULT_CONNECTION_SPEED 0
41 GST_DEBUG_CATEGORY_STATIC (mmssrc_debug);
42 #define GST_CAT_DEFAULT mmssrc_debug
44 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
47 GST_STATIC_CAPS ("video/x-ms-asf")
50 static void gst_mms_finalize (GObject * gobject);
51 static void gst_mms_uri_handler_init (gpointer g_iface, gpointer iface_data);
53 static void gst_mms_set_property (GObject * object, guint prop_id,
54 const GValue * value, GParamSpec * pspec);
55 static void gst_mms_get_property (GObject * object, guint prop_id,
56 GValue * value, GParamSpec * pspec);
58 static gboolean gst_mms_query (GstBaseSrc * src, GstQuery * query);
60 static gboolean gst_mms_start (GstBaseSrc * bsrc);
61 static gboolean gst_mms_stop (GstBaseSrc * bsrc);
62 static gboolean gst_mms_is_seekable (GstBaseSrc * src);
63 static gboolean gst_mms_get_size (GstBaseSrc * src, guint64 * size);
64 static gboolean gst_mms_prepare_seek_segment (GstBaseSrc * src,
65 GstEvent * event, GstSegment * segment);
66 static gboolean gst_mms_do_seek (GstBaseSrc * src, GstSegment * segment);
68 static GstFlowReturn gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf);
70 static gboolean gst_mms_uri_set_uri (GstURIHandler * handler,
74 gst_mms_urihandler_init (GType mms_type)
76 static const GInterfaceInfo urihandler_info = {
77 gst_mms_uri_handler_init,
82 g_type_add_interface_static (mms_type, GST_TYPE_URI_HANDLER,
86 GST_BOILERPLATE_FULL (GstMMS, gst_mms, GstPushSrc, GST_TYPE_PUSH_SRC,
87 gst_mms_urihandler_init);
90 gst_mms_base_init (gpointer g_class)
92 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
94 gst_element_class_add_static_pad_template (element_class, &src_factory);
95 gst_element_class_set_details_simple (element_class, "MMS streaming source",
97 "Receive data streamed via MSFT Multi Media Server protocol",
98 "Maciej Katafiasz <mathrick@users.sourceforge.net>");
100 GST_DEBUG_CATEGORY_INIT (mmssrc_debug, "mmssrc", 0, "MMS Source Element");
103 /* initialize the plugin's class */
105 gst_mms_class_init (GstMMSClass * klass)
107 GObjectClass *gobject_class = (GObjectClass *) klass;
108 GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass;
109 GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass;
111 gobject_class->set_property = gst_mms_set_property;
112 gobject_class->get_property = gst_mms_get_property;
113 gobject_class->finalize = gst_mms_finalize;
115 g_object_class_install_property (gobject_class, PROP_LOCATION,
116 g_param_spec_string ("location", "location",
117 "Host URL to connect to. Accepted are mms://, mmsu://, mmst:// URL types",
118 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
120 g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED,
121 g_param_spec_uint ("connection-speed", "Connection Speed",
122 "Network connection speed in kbps (0 = unknown)",
123 0, G_MAXINT / 1000, DEFAULT_CONNECTION_SPEED,
124 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
125 /* Note: connection-speed is intentionaly limited to G_MAXINT as libmms use int for it */
127 gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_mms_start);
128 gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_mms_stop);
130 gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_mms_create);
132 gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_mms_is_seekable);
133 gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_mms_get_size);
134 gstbasesrc_class->prepare_seek_segment =
135 GST_DEBUG_FUNCPTR (gst_mms_prepare_seek_segment);
136 gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_mms_do_seek);
137 gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_mms_query);
140 /* initialize the new element
141 * instantiate pads and add them to element
143 * initialize structure
146 gst_mms_init (GstMMS * mmssrc, GstMMSClass * g_class)
148 mmssrc->uri_name = NULL;
149 mmssrc->current_connection_uri_name = NULL;
150 mmssrc->connection = NULL;
151 mmssrc->connection_speed = DEFAULT_CONNECTION_SPEED;
155 gst_mms_finalize (GObject * gobject)
157 GstMMS *mmssrc = GST_MMS (gobject);
159 /* We may still have a connection open, as we preserve unused / pristine
160 open connections in stop to reuse them in start. */
161 if (mmssrc->connection) {
162 mmsx_close (mmssrc->connection);
163 mmssrc->connection = NULL;
166 if (mmssrc->current_connection_uri_name) {
167 g_free (mmssrc->current_connection_uri_name);
168 mmssrc->current_connection_uri_name = NULL;
171 if (mmssrc->uri_name) {
172 g_free (mmssrc->uri_name);
173 mmssrc->uri_name = NULL;
176 G_OBJECT_CLASS (parent_class)->finalize (gobject);
179 /* FIXME operating in TIME rather than BYTES could remove this altogether
180 * and be more convenient elsewhere */
182 gst_mms_query (GstBaseSrc * src, GstQuery * query)
184 GstMMS *mmssrc = GST_MMS (src);
189 switch (GST_QUERY_TYPE (query)) {
190 case GST_QUERY_POSITION:
191 gst_query_parse_position (query, &format, &value);
192 if (format != GST_FORMAT_BYTES) {
196 value = (gint64) mmsx_get_current_pos (mmssrc->connection);
197 gst_query_set_position (query, format, value);
199 case GST_QUERY_DURATION:
200 if (!mmsx_get_seekable (mmssrc->connection)) {
204 gst_query_parse_duration (query, &format, &value);
206 case GST_FORMAT_BYTES:
207 value = (gint64) mmsx_get_length (mmssrc->connection);
208 gst_query_set_duration (query, format, value);
210 case GST_FORMAT_TIME:
211 value = mmsx_get_time_length (mmssrc->connection) * GST_SECOND;
212 gst_query_set_duration (query, format, value);
219 /* chain to parent */
221 GST_BASE_SRC_CLASS (parent_class)->query (GST_BASE_SRC (src), query);
230 gst_mms_prepare_seek_segment (GstBaseSrc * src, GstEvent * event,
231 GstSegment * segment)
233 GstSeekType cur_type, stop_type;
236 GstFormat seek_format;
239 gst_event_parse_seek (event, &rate, &seek_format, &flags,
240 &cur_type, &cur, &stop_type, &stop);
242 if (seek_format != GST_FORMAT_BYTES && seek_format != GST_FORMAT_TIME) {
243 GST_LOG_OBJECT (src, "Only byte or time seeking is supported");
247 if (stop_type != GST_SEEK_TYPE_NONE) {
248 GST_LOG_OBJECT (src, "Stop seeking not supported");
252 if (cur_type != GST_SEEK_TYPE_NONE && cur_type != GST_SEEK_TYPE_SET) {
253 GST_LOG_OBJECT (src, "Only absolute seeking is supported");
257 /* We would like to convert from GST_FORMAT_TIME to GST_FORMAT_BYTES here
258 when needed, but we cannot as to do that we need to actually do the seek,
259 so we handle this in do_seek instead. */
261 /* FIXME implement relative seeking, we could do any needed relevant
262 seeking calculations here (in seek_format metrics), before the re-init
265 gst_segment_init (segment, seek_format);
266 gst_segment_set_seek (segment, rate, seek_format, flags, cur_type, cur,
267 stop_type, stop, NULL);
273 gst_mms_do_seek (GstBaseSrc * src, GstSegment * segment)
276 GstMMS *mmssrc = GST_MMS (src);
278 if (segment->format == GST_FORMAT_TIME) {
279 if (!mmsx_time_seek (NULL, mmssrc->connection,
280 (double) segment->start / GST_SECOND)) {
281 GST_LOG_OBJECT (mmssrc, "mmsx_time_seek() failed");
284 start = mmsx_get_current_pos (mmssrc->connection);
285 GST_INFO_OBJECT (mmssrc, "sought to %" GST_TIME_FORMAT ", offset after "
286 "seek: %" G_GINT64_FORMAT, GST_TIME_ARGS (segment->start), start);
287 } else if (segment->format == GST_FORMAT_BYTES) {
288 start = mmsx_seek (NULL, mmssrc->connection, segment->start, SEEK_SET);
289 /* mmsx_seek will close and reopen the connection when seeking with the
290 mmsh protocol, if the reopening fails this is indicated with -1 */
292 GST_DEBUG_OBJECT (mmssrc, "connection broken during seek");
295 GST_INFO_OBJECT (mmssrc, "sought to: %" G_GINT64_FORMAT " bytes, "
296 "result: %" G_GINT64_FORMAT, segment->start, start);
298 GST_DEBUG_OBJECT (mmssrc, "unsupported seek segment format: %s",
299 GST_STR_NULL (gst_format_get_name (segment->format)));
302 gst_segment_init (segment, GST_FORMAT_BYTES);
303 gst_segment_set_seek (segment, segment->rate, GST_FORMAT_BYTES,
304 segment->flags, GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_NONE,
305 segment->stop, NULL);
311 * this function generates new data when needed
316 gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf)
318 GstMMS *mmssrc = GST_MMS (psrc);
326 offset = mmsx_get_current_pos (mmssrc->connection);
328 /* Check if a seek perhaps has wrecked our connection */
330 GST_ERROR_OBJECT (mmssrc,
331 "connection broken (probably an error during mmsx_seek_time during a convert query) returning FLOW_ERROR");
332 return GST_FLOW_ERROR;
335 /* Choose blocksize best for optimum performance */
337 blocksize = mmsx_get_asf_header_len (mmssrc->connection);
339 blocksize = mmsx_get_asf_packet_len (mmssrc->connection);
341 *buf = gst_buffer_try_new_and_alloc (blocksize);
343 GST_ERROR_OBJECT (mmssrc, "Failed to allocate %u bytes", blocksize);
344 return GST_FLOW_ERROR;
347 data = GST_BUFFER_DATA (*buf);
348 GST_BUFFER_SIZE (*buf) = 0;
349 GST_LOG_OBJECT (mmssrc, "reading %d bytes", blocksize);
350 result = mmsx_read (NULL, mmssrc->connection, (char *) data, blocksize);
356 GST_BUFFER_OFFSET (*buf) = offset;
357 GST_BUFFER_SIZE (*buf) = result;
359 GST_LOG_OBJECT (mmssrc, "Returning buffer with offset %" G_GINT64_FORMAT
360 " and size %u", GST_BUFFER_OFFSET (*buf), GST_BUFFER_SIZE (*buf));
362 gst_buffer_set_caps (*buf, GST_PAD_CAPS (GST_BASE_SRC_PAD (mmssrc)));
368 GST_DEBUG_OBJECT (mmssrc, "EOS");
369 gst_buffer_unref (*buf);
371 return GST_FLOW_UNEXPECTED;
376 gst_mms_is_seekable (GstBaseSrc * src)
378 GstMMS *mmssrc = GST_MMS (src);
380 return mmsx_get_seekable (mmssrc->connection);
384 gst_mms_get_size (GstBaseSrc * src, guint64 * size)
386 GstMMS *mmssrc = GST_MMS (src);
388 /* non seekable usually means live streams, and get_length() returns,
389 erm, interesting values for live streams */
390 if (!mmsx_get_seekable (mmssrc->connection))
393 *size = mmsx_get_length (mmssrc->connection);
398 gst_mms_start (GstBaseSrc * bsrc)
400 GstMMS *mms = GST_MMS (bsrc);
401 guint bandwidth_avail;
403 if (!mms->uri_name || *mms->uri_name == '\0')
406 if (mms->connection_speed)
407 bandwidth_avail = mms->connection_speed;
409 bandwidth_avail = G_MAXINT;
411 /* If we already have a connection, and the uri isn't changed, reuse it,
412 as connecting is expensive. */
413 if (mms->connection) {
414 if (!strcmp (mms->uri_name, mms->current_connection_uri_name)) {
415 GST_DEBUG_OBJECT (mms, "Reusing existing connection for %s",
419 mmsx_close (mms->connection);
420 g_free (mms->current_connection_uri_name);
421 mms->current_connection_uri_name = NULL;
425 /* FIXME: pass some sane arguments here */
426 GST_DEBUG_OBJECT (mms,
427 "Trying mms_connect (%s) with bandwidth constraint of %d bps",
428 mms->uri_name, bandwidth_avail);
429 mms->connection = mmsx_connect (NULL, NULL, mms->uri_name, bandwidth_avail);
430 if (mms->connection) {
431 /* Save the uri name so that it can be checked for connection reusing,
433 mms->current_connection_uri_name = g_strdup (mms->uri_name);
434 GST_DEBUG_OBJECT (mms, "Connect successful");
437 gchar *url, *location;
439 GST_ERROR_OBJECT (mms,
440 "Could not connect to this stream, redirecting to rtsp");
441 location = strstr (mms->uri_name, "://");
442 if (location == NULL || *location == '\0' || *(location + 3) == '\0')
444 url = g_strdup_printf ("rtsp://%s", location + 3);
446 gst_element_post_message (GST_ELEMENT_CAST (mms),
447 gst_message_new_element (GST_OBJECT_CAST (mms),
448 gst_structure_new ("redirect", "new-location", G_TYPE_STRING, url,
451 /* post an error message as well, so that applications that don't handle
452 * redirect messages get to see a proper error message */
453 GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
454 ("Could not connect to streaming server."),
455 ("A redirect message was posted on the bus and should have been "
456 "handled by the application."));
463 GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
464 ("No URI to open specified"), (NULL));
470 gst_mms_stop (GstBaseSrc * bsrc)
472 GstMMS *mms = GST_MMS (bsrc);
474 if (mms->connection != NULL) {
475 /* Check if the connection is still pristine, that is if no more then
476 just the mmslib cached asf header has been read. If it is still pristine
477 preserve it as we often are re-started with the same URL and connecting
479 if (mmsx_get_current_pos (mms->connection) >
480 mmsx_get_asf_header_len (mms->connection)) {
481 mmsx_close (mms->connection);
482 mms->connection = NULL;
483 g_free (mms->current_connection_uri_name);
484 mms->current_connection_uri_name = NULL;
491 gst_mms_set_property (GObject * object, guint prop_id,
492 const GValue * value, GParamSpec * pspec)
494 GstMMS *mmssrc = GST_MMS (object);
498 gst_mms_uri_set_uri (GST_URI_HANDLER (mmssrc),
499 g_value_get_string (value));
501 case PROP_CONNECTION_SPEED:
502 GST_OBJECT_LOCK (mmssrc);
503 mmssrc->connection_speed = g_value_get_uint (value) * 1000;
504 GST_OBJECT_UNLOCK (mmssrc);
507 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
513 gst_mms_get_property (GObject * object, guint prop_id,
514 GValue * value, GParamSpec * pspec)
516 GstMMS *mmssrc = GST_MMS (object);
518 GST_OBJECT_LOCK (mmssrc);
521 if (mmssrc->uri_name)
522 g_value_set_string (value, mmssrc->uri_name);
524 case PROP_CONNECTION_SPEED:
525 g_value_set_uint (value, mmssrc->connection_speed / 1000);
528 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
531 GST_OBJECT_UNLOCK (mmssrc);
534 /* entry point to initialize the plug-in
535 * initialize the plug-in itself
536 * register the element factories and pad templates
537 * register the features
540 plugin_init (GstPlugin * plugin)
542 return gst_element_register (plugin, "mmssrc", GST_RANK_NONE, GST_TYPE_MMS);
546 gst_mms_uri_get_type (void)
552 gst_mms_uri_get_protocols (void)
554 static const gchar *protocols[] = { "mms", "mmsh", "mmst", "mmsu", NULL };
556 return (gchar **) protocols;
560 gst_mms_uri_get_uri (GstURIHandler * handler)
562 GstMMS *src = GST_MMS (handler);
564 return src->uri_name;
568 gst_mms_src_make_valid_uri (const gchar * uri)
571 const gchar *colon, *tmp;
574 if (!uri || !gst_uri_is_valid (uri))
577 protocol = gst_uri_get_protocol (uri);
579 if ((strcmp (protocol, "mms") != 0) && (strcmp (protocol, "mmsh") != 0) &&
580 (strcmp (protocol, "mmst") != 0) && (strcmp (protocol, "mmsu") != 0)) {
586 colon = strstr (uri, "://");
595 /* libmms segfaults if there's no hostname or
596 * no / after the hostname
598 colon = strstr (tmp, "/");
602 if (strstr (tmp, "/") == NULL) {
606 ret = g_malloc0 (len + 2);
607 memcpy (ret, uri, len);
611 return g_strdup (uri);
616 gst_mms_uri_set_uri (GstURIHandler * handler, const gchar * uri)
618 GstMMS *src = GST_MMS (handler);
621 fixed_uri = gst_mms_src_make_valid_uri (uri);
622 if (!fixed_uri && uri)
625 GST_OBJECT_LOCK (src);
627 g_free (src->uri_name);
628 src->uri_name = fixed_uri;
629 GST_OBJECT_UNLOCK (src);
635 gst_mms_uri_handler_init (gpointer g_iface, gpointer iface_data)
637 GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
639 iface->get_type = gst_mms_uri_get_type;
640 iface->get_protocols = gst_mms_uri_get_protocols;
641 iface->get_uri = gst_mms_uri_get_uri;
642 iface->set_uri = gst_mms_uri_set_uri;
646 /* this is the structure that gst-register looks for
647 * so keep the name plugin_desc, or you cannot get your plug-in registered */
648 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
651 "Microsoft Multi Media Server streaming protocol support",
652 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)