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., 51 Franklin St, Fifth Floor,
19 * Boston, MA 02110-1301, 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,
71 const gchar * uri, GError ** error);
73 #define gst_mms_parent_class parent_class
74 G_DEFINE_TYPE_WITH_CODE (GstMMS, gst_mms, GST_TYPE_PUSH_SRC,
75 G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, gst_mms_uri_handler_init));
77 /* initialize the plugin's class */
79 gst_mms_class_init (GstMMSClass * klass)
81 GObjectClass *gobject_class = (GObjectClass *) klass;
82 GstElementClass *gstelement_class = (GstElementClass *) klass;
83 GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass;
84 GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass;
86 gobject_class->set_property = gst_mms_set_property;
87 gobject_class->get_property = gst_mms_get_property;
88 gobject_class->finalize = gst_mms_finalize;
90 g_object_class_install_property (gobject_class, PROP_LOCATION,
91 g_param_spec_string ("location", "location",
92 "Host URL to connect to. Accepted are mms://, mmsu://, mmst:// URL types",
93 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
95 /* Note: connection-speed is intentionaly limited to G_MAXINT as libmms
97 g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED,
98 g_param_spec_uint64 ("connection-speed", "Connection Speed",
99 "Network connection speed in kbps (0 = unknown)",
100 0, G_MAXINT / 1000, DEFAULT_CONNECTION_SPEED,
101 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
103 gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
105 gst_element_class_set_static_metadata (gstelement_class,
106 "MMS streaming source", "Source/Network",
107 "Receive data streamed via MSFT Multi Media Server protocol",
108 "Maciej Katafiasz <mathrick@users.sourceforge.net>");
110 GST_DEBUG_CATEGORY_INIT (mmssrc_debug, "mmssrc", 0, "MMS Source Element");
112 gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_mms_start);
113 gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_mms_stop);
115 gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_mms_create);
117 gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_mms_is_seekable);
118 gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_mms_get_size);
119 gstbasesrc_class->prepare_seek_segment =
120 GST_DEBUG_FUNCPTR (gst_mms_prepare_seek_segment);
121 gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_mms_do_seek);
122 gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_mms_query);
125 /* initialize the new element
126 * instantiate pads and add them to element
128 * initialize structure
131 gst_mms_init (GstMMS * mmssrc)
133 mmssrc->uri_name = NULL;
134 mmssrc->current_connection_uri_name = NULL;
135 mmssrc->connection = NULL;
136 mmssrc->connection_speed = DEFAULT_CONNECTION_SPEED;
140 gst_mms_finalize (GObject * gobject)
142 GstMMS *mmssrc = GST_MMS (gobject);
144 /* We may still have a connection open, as we preserve unused / pristine
145 open connections in stop to reuse them in start. */
146 if (mmssrc->connection) {
147 mmsx_close (mmssrc->connection);
148 mmssrc->connection = NULL;
151 if (mmssrc->current_connection_uri_name) {
152 g_free (mmssrc->current_connection_uri_name);
153 mmssrc->current_connection_uri_name = NULL;
156 if (mmssrc->uri_name) {
157 g_free (mmssrc->uri_name);
158 mmssrc->uri_name = NULL;
161 G_OBJECT_CLASS (parent_class)->finalize (gobject);
164 /* FIXME operating in TIME rather than BYTES could remove this altogether
165 * and be more convenient elsewhere */
167 gst_mms_query (GstBaseSrc * src, GstQuery * query)
169 GstMMS *mmssrc = GST_MMS (src);
174 switch (GST_QUERY_TYPE (query)) {
175 case GST_QUERY_POSITION:
176 gst_query_parse_position (query, &format, &value);
177 if (format != GST_FORMAT_BYTES) {
181 value = (gint64) mmsx_get_current_pos (mmssrc->connection);
182 gst_query_set_position (query, format, value);
184 case GST_QUERY_DURATION:
185 if (!mmsx_get_seekable (mmssrc->connection)) {
189 gst_query_parse_duration (query, &format, &value);
191 case GST_FORMAT_BYTES:
192 value = (gint64) mmsx_get_length (mmssrc->connection);
193 gst_query_set_duration (query, format, value);
195 case GST_FORMAT_TIME:
196 value = mmsx_get_time_length (mmssrc->connection) * GST_SECOND;
197 gst_query_set_duration (query, format, value);
204 /* chain to parent */
206 GST_BASE_SRC_CLASS (parent_class)->query (GST_BASE_SRC (src), query);
215 gst_mms_prepare_seek_segment (GstBaseSrc * src, GstEvent * event,
216 GstSegment * segment)
218 GstSeekType cur_type, stop_type;
221 GstFormat seek_format;
224 gst_event_parse_seek (event, &rate, &seek_format, &flags,
225 &cur_type, &cur, &stop_type, &stop);
227 if (seek_format != GST_FORMAT_BYTES && seek_format != GST_FORMAT_TIME) {
228 GST_LOG_OBJECT (src, "Only byte or time seeking is supported");
232 if (stop_type != GST_SEEK_TYPE_NONE) {
233 GST_LOG_OBJECT (src, "Stop seeking not supported");
237 if (cur_type != GST_SEEK_TYPE_NONE && cur_type != GST_SEEK_TYPE_SET) {
238 GST_LOG_OBJECT (src, "Only absolute seeking is supported");
242 /* We would like to convert from GST_FORMAT_TIME to GST_FORMAT_BYTES here
243 when needed, but we cannot as to do that we need to actually do the seek,
244 so we handle this in do_seek instead. */
246 /* FIXME implement relative seeking, we could do any needed relevant
247 seeking calculations here (in seek_format metrics), before the re-init
250 gst_segment_init (segment, seek_format);
251 gst_segment_do_seek (segment, rate, seek_format, flags, cur_type, cur,
252 stop_type, stop, NULL);
258 gst_mms_do_seek (GstBaseSrc * src, GstSegment * segment)
261 GstMMS *mmssrc = GST_MMS (src);
263 if (segment->format == GST_FORMAT_TIME) {
264 if (!mmsx_time_seek (NULL, mmssrc->connection,
265 (double) segment->start / GST_SECOND)) {
266 GST_LOG_OBJECT (mmssrc, "mmsx_time_seek() failed");
269 start = mmsx_get_current_pos (mmssrc->connection);
270 GST_INFO_OBJECT (mmssrc,
271 "performed seek to %" GST_TIME_FORMAT ", offset after " "seek: %"
272 G_GINT64_FORMAT, GST_TIME_ARGS (segment->start), start);
273 } else if (segment->format == GST_FORMAT_BYTES) {
274 start = mmsx_seek (NULL, mmssrc->connection, segment->start, SEEK_SET);
275 /* mmsx_seek will close and reopen the connection when seeking with the
276 mmsh protocol, if the reopening fails this is indicated with -1 */
278 GST_DEBUG_OBJECT (mmssrc, "connection broken during seek");
281 GST_INFO_OBJECT (mmssrc, "performed seek to: %" G_GINT64_FORMAT " bytes, "
282 "result: %" G_GINT64_FORMAT, segment->start, start);
284 GST_DEBUG_OBJECT (mmssrc, "unsupported seek segment format: %s",
285 GST_STR_NULL (gst_format_get_name (segment->format)));
288 gst_segment_init (segment, GST_FORMAT_BYTES);
289 gst_segment_do_seek (segment, segment->rate, GST_FORMAT_BYTES,
290 GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_NONE,
291 segment->stop, NULL);
297 * this function generates new data when needed
302 gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf)
304 GstMMS *mmssrc = GST_MMS (psrc);
312 offset = mmsx_get_current_pos (mmssrc->connection);
314 /* Check if a seek perhaps has wrecked our connection */
316 GST_ERROR_OBJECT (mmssrc,
317 "connection broken (probably an error during mmsx_seek_time during a convert query) returning FLOW_ERROR");
318 return GST_FLOW_ERROR;
321 /* Choose blocksize best for optimum performance */
323 blocksize = mmsx_get_asf_header_len (mmssrc->connection);
325 blocksize = mmsx_get_asf_packet_len (mmssrc->connection);
327 data = g_try_malloc (blocksize);
329 GST_ERROR_OBJECT (mmssrc, "Failed to allocate %u bytes", blocksize);
330 return GST_FLOW_ERROR;
333 GST_LOG_OBJECT (mmssrc, "reading %d bytes", blocksize);
334 result = mmsx_read (NULL, mmssrc->connection, (char *) data, blocksize);
339 *buf = gst_buffer_new_wrapped (data, result);
340 GST_BUFFER_OFFSET (*buf) = offset;
342 GST_LOG_OBJECT (mmssrc, "Returning buffer with offset %" G_GOFFSET_FORMAT
343 " and size %u", offset, result);
349 GST_DEBUG_OBJECT (mmssrc, "EOS");
357 gst_mms_is_seekable (GstBaseSrc * src)
359 GstMMS *mmssrc = GST_MMS (src);
361 return mmsx_get_seekable (mmssrc->connection);
365 gst_mms_get_size (GstBaseSrc * src, guint64 * size)
367 GstMMS *mmssrc = GST_MMS (src);
369 /* non seekable usually means live streams, and get_length() returns,
370 erm, interesting values for live streams */
371 if (!mmsx_get_seekable (mmssrc->connection))
374 *size = mmsx_get_length (mmssrc->connection);
379 gst_mms_start (GstBaseSrc * bsrc)
381 GstMMS *mms = GST_MMS (bsrc);
382 guint bandwidth_avail;
384 if (!mms->uri_name || *mms->uri_name == '\0')
387 if (mms->connection_speed)
388 bandwidth_avail = mms->connection_speed;
390 bandwidth_avail = G_MAXINT;
392 /* If we already have a connection, and the uri isn't changed, reuse it,
393 as connecting is expensive. */
394 if (mms->connection) {
395 if (!strcmp (mms->uri_name, mms->current_connection_uri_name)) {
396 GST_DEBUG_OBJECT (mms, "Reusing existing connection for %s",
400 mmsx_close (mms->connection);
401 g_free (mms->current_connection_uri_name);
402 mms->current_connection_uri_name = NULL;
406 /* FIXME: pass some sane arguments here */
407 GST_DEBUG_OBJECT (mms,
408 "Trying mms_connect (%s) with bandwidth constraint of %d bps",
409 mms->uri_name, bandwidth_avail);
410 mms->connection = mmsx_connect (NULL, NULL, mms->uri_name, bandwidth_avail);
411 if (mms->connection) {
412 /* Save the uri name so that it can be checked for connection reusing,
414 mms->current_connection_uri_name = g_strdup (mms->uri_name);
415 GST_DEBUG_OBJECT (mms, "Connect successful");
418 gchar *url, *location;
420 GST_ERROR_OBJECT (mms,
421 "Could not connect to this stream, redirecting to rtsp");
422 location = strstr (mms->uri_name, "://");
423 if (location == NULL || *location == '\0' || *(location + 3) == '\0')
425 url = g_strdup_printf ("rtsp://%s", location + 3);
427 gst_element_post_message (GST_ELEMENT_CAST (mms),
428 gst_message_new_element (GST_OBJECT_CAST (mms),
429 gst_structure_new ("redirect", "new-location", G_TYPE_STRING, url,
432 /* post an error message as well, so that applications that don't handle
433 * redirect messages get to see a proper error message */
434 GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
435 ("Could not connect to streaming server."),
436 ("A redirect message was posted on the bus and should have been "
437 "handled by the application."));
444 GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
445 ("No URI to open specified"), (NULL));
451 gst_mms_stop (GstBaseSrc * bsrc)
453 GstMMS *mms = GST_MMS (bsrc);
455 if (mms->connection != NULL) {
456 /* Check if the connection is still pristine, that is if no more then
457 just the mmslib cached asf header has been read. If it is still pristine
458 preserve it as we often are re-started with the same URL and connecting
460 if (mmsx_get_current_pos (mms->connection) >
461 mmsx_get_asf_header_len (mms->connection)) {
462 mmsx_close (mms->connection);
463 mms->connection = NULL;
464 g_free (mms->current_connection_uri_name);
465 mms->current_connection_uri_name = NULL;
472 gst_mms_set_property (GObject * object, guint prop_id,
473 const GValue * value, GParamSpec * pspec)
475 GstMMS *mmssrc = GST_MMS (object);
479 gst_mms_uri_set_uri (GST_URI_HANDLER (mmssrc),
480 g_value_get_string (value), NULL);
482 case PROP_CONNECTION_SPEED:
483 GST_OBJECT_LOCK (mmssrc);
484 mmssrc->connection_speed = g_value_get_uint64 (value) * 1000;
485 GST_OBJECT_UNLOCK (mmssrc);
488 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
494 gst_mms_get_property (GObject * object, guint prop_id,
495 GValue * value, GParamSpec * pspec)
497 GstMMS *mmssrc = GST_MMS (object);
499 GST_OBJECT_LOCK (mmssrc);
502 if (mmssrc->uri_name)
503 g_value_set_string (value, mmssrc->uri_name);
505 case PROP_CONNECTION_SPEED:
506 g_value_set_uint64 (value, mmssrc->connection_speed / 1000);
509 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
512 GST_OBJECT_UNLOCK (mmssrc);
515 /* entry point to initialize the plug-in
516 * initialize the plug-in itself
517 * register the element factories and pad templates
518 * register the features
521 plugin_init (GstPlugin * plugin)
523 return gst_element_register (plugin, "mmssrc", GST_RANK_NONE, GST_TYPE_MMS);
527 gst_mms_uri_get_type (GType type)
532 static const gchar *const *
533 gst_mms_uri_get_protocols (GType type)
535 static const gchar *protocols[] = { "mms", "mmsh", "mmst", "mmsu", NULL };
541 gst_mms_uri_get_uri (GstURIHandler * handler)
543 GstMMS *src = GST_MMS (handler);
545 /* FIXME: make thread-safe */
546 return g_strdup (src->uri_name);
550 gst_mms_src_make_valid_uri (const gchar * uri)
553 const gchar *colon, *tmp;
556 if (!uri || !gst_uri_is_valid (uri))
559 protocol = gst_uri_get_protocol (uri);
561 if ((strcmp (protocol, "mms") != 0) && (strcmp (protocol, "mmsh") != 0) &&
562 (strcmp (protocol, "mmst") != 0) && (strcmp (protocol, "mmsu") != 0)) {
568 colon = strstr (uri, "://");
577 /* libmms segfaults if there's no hostname or
578 * no / after the hostname
580 colon = strstr (tmp, "/");
584 if (strstr (tmp, "/") == NULL) {
588 ret = g_malloc0 (len + 2);
589 memcpy (ret, uri, len);
593 return g_strdup (uri);
598 gst_mms_uri_set_uri (GstURIHandler * handler, const gchar * uri,
601 GstMMS *src = GST_MMS (handler);
604 fixed_uri = gst_mms_src_make_valid_uri (uri);
605 if (!fixed_uri && uri) {
606 g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
611 GST_OBJECT_LOCK (src);
612 g_free (src->uri_name);
613 src->uri_name = fixed_uri;
614 GST_OBJECT_UNLOCK (src);
620 gst_mms_uri_handler_init (gpointer g_iface, gpointer iface_data)
622 GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
624 iface->get_type = gst_mms_uri_get_type;
625 iface->get_protocols = gst_mms_uri_get_protocols;
626 iface->get_uri = gst_mms_uri_get_uri;
627 iface->set_uri = gst_mms_uri_set_uri;
631 /* this is the structure that gst-register looks for
632 * so keep the name plugin_desc, or you cannot get your plug-in registered */
633 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
636 "Microsoft Multi Media Server streaming protocol support",
637 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)