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, "sought to %" GST_TIME_FORMAT ", offset after "
271 "seek: %" G_GINT64_FORMAT, GST_TIME_ARGS (segment->start), start);
272 } else if (segment->format == GST_FORMAT_BYTES) {
273 start = mmsx_seek (NULL, mmssrc->connection, segment->start, SEEK_SET);
274 /* mmsx_seek will close and reopen the connection when seeking with the
275 mmsh protocol, if the reopening fails this is indicated with -1 */
277 GST_DEBUG_OBJECT (mmssrc, "connection broken during seek");
280 GST_INFO_OBJECT (mmssrc, "sought to: %" G_GINT64_FORMAT " bytes, "
281 "result: %" G_GINT64_FORMAT, segment->start, start);
283 GST_DEBUG_OBJECT (mmssrc, "unsupported seek segment format: %s",
284 GST_STR_NULL (gst_format_get_name (segment->format)));
287 gst_segment_init (segment, GST_FORMAT_BYTES);
288 gst_segment_do_seek (segment, segment->rate, GST_FORMAT_BYTES,
289 GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_NONE,
290 segment->stop, NULL);
296 * this function generates new data when needed
301 gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf)
303 GstMMS *mmssrc = GST_MMS (psrc);
311 offset = mmsx_get_current_pos (mmssrc->connection);
313 /* Check if a seek perhaps has wrecked our connection */
315 GST_ERROR_OBJECT (mmssrc,
316 "connection broken (probably an error during mmsx_seek_time during a convert query) returning FLOW_ERROR");
317 return GST_FLOW_ERROR;
320 /* Choose blocksize best for optimum performance */
322 blocksize = mmsx_get_asf_header_len (mmssrc->connection);
324 blocksize = mmsx_get_asf_packet_len (mmssrc->connection);
326 data = g_try_malloc (blocksize);
328 GST_ERROR_OBJECT (mmssrc, "Failed to allocate %u bytes", blocksize);
329 return GST_FLOW_ERROR;
332 GST_LOG_OBJECT (mmssrc, "reading %d bytes", blocksize);
333 result = mmsx_read (NULL, mmssrc->connection, (char *) data, blocksize);
338 *buf = gst_buffer_new_wrapped (data, result);
339 GST_BUFFER_OFFSET (*buf) = offset;
341 GST_LOG_OBJECT (mmssrc, "Returning buffer with offset %" G_GOFFSET_FORMAT
342 " and size %u", offset, result);
348 GST_DEBUG_OBJECT (mmssrc, "EOS");
356 gst_mms_is_seekable (GstBaseSrc * src)
358 GstMMS *mmssrc = GST_MMS (src);
360 return mmsx_get_seekable (mmssrc->connection);
364 gst_mms_get_size (GstBaseSrc * src, guint64 * size)
366 GstMMS *mmssrc = GST_MMS (src);
368 /* non seekable usually means live streams, and get_length() returns,
369 erm, interesting values for live streams */
370 if (!mmsx_get_seekable (mmssrc->connection))
373 *size = mmsx_get_length (mmssrc->connection);
378 gst_mms_start (GstBaseSrc * bsrc)
380 GstMMS *mms = GST_MMS (bsrc);
381 guint bandwidth_avail;
383 if (!mms->uri_name || *mms->uri_name == '\0')
386 if (mms->connection_speed)
387 bandwidth_avail = mms->connection_speed;
389 bandwidth_avail = G_MAXINT;
391 /* If we already have a connection, and the uri isn't changed, reuse it,
392 as connecting is expensive. */
393 if (mms->connection) {
394 if (!strcmp (mms->uri_name, mms->current_connection_uri_name)) {
395 GST_DEBUG_OBJECT (mms, "Reusing existing connection for %s",
399 mmsx_close (mms->connection);
400 g_free (mms->current_connection_uri_name);
401 mms->current_connection_uri_name = NULL;
405 /* FIXME: pass some sane arguments here */
406 GST_DEBUG_OBJECT (mms,
407 "Trying mms_connect (%s) with bandwidth constraint of %d bps",
408 mms->uri_name, bandwidth_avail);
409 mms->connection = mmsx_connect (NULL, NULL, mms->uri_name, bandwidth_avail);
410 if (mms->connection) {
411 /* Save the uri name so that it can be checked for connection reusing,
413 mms->current_connection_uri_name = g_strdup (mms->uri_name);
414 GST_DEBUG_OBJECT (mms, "Connect successful");
417 gchar *url, *location;
419 GST_ERROR_OBJECT (mms,
420 "Could not connect to this stream, redirecting to rtsp");
421 location = strstr (mms->uri_name, "://");
422 if (location == NULL || *location == '\0' || *(location + 3) == '\0')
424 url = g_strdup_printf ("rtsp://%s", location + 3);
426 gst_element_post_message (GST_ELEMENT_CAST (mms),
427 gst_message_new_element (GST_OBJECT_CAST (mms),
428 gst_structure_new ("redirect", "new-location", G_TYPE_STRING, url,
431 /* post an error message as well, so that applications that don't handle
432 * redirect messages get to see a proper error message */
433 GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
434 ("Could not connect to streaming server."),
435 ("A redirect message was posted on the bus and should have been "
436 "handled by the application."));
443 GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
444 ("No URI to open specified"), (NULL));
450 gst_mms_stop (GstBaseSrc * bsrc)
452 GstMMS *mms = GST_MMS (bsrc);
454 if (mms->connection != NULL) {
455 /* Check if the connection is still pristine, that is if no more then
456 just the mmslib cached asf header has been read. If it is still pristine
457 preserve it as we often are re-started with the same URL and connecting
459 if (mmsx_get_current_pos (mms->connection) >
460 mmsx_get_asf_header_len (mms->connection)) {
461 mmsx_close (mms->connection);
462 mms->connection = NULL;
463 g_free (mms->current_connection_uri_name);
464 mms->current_connection_uri_name = NULL;
471 gst_mms_set_property (GObject * object, guint prop_id,
472 const GValue * value, GParamSpec * pspec)
474 GstMMS *mmssrc = GST_MMS (object);
478 gst_mms_uri_set_uri (GST_URI_HANDLER (mmssrc),
479 g_value_get_string (value), NULL);
481 case PROP_CONNECTION_SPEED:
482 GST_OBJECT_LOCK (mmssrc);
483 mmssrc->connection_speed = g_value_get_uint64 (value) * 1000;
484 GST_OBJECT_UNLOCK (mmssrc);
487 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
493 gst_mms_get_property (GObject * object, guint prop_id,
494 GValue * value, GParamSpec * pspec)
496 GstMMS *mmssrc = GST_MMS (object);
498 GST_OBJECT_LOCK (mmssrc);
501 if (mmssrc->uri_name)
502 g_value_set_string (value, mmssrc->uri_name);
504 case PROP_CONNECTION_SPEED:
505 g_value_set_uint64 (value, mmssrc->connection_speed / 1000);
508 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
511 GST_OBJECT_UNLOCK (mmssrc);
514 /* entry point to initialize the plug-in
515 * initialize the plug-in itself
516 * register the element factories and pad templates
517 * register the features
520 plugin_init (GstPlugin * plugin)
522 return gst_element_register (plugin, "mmssrc", GST_RANK_NONE, GST_TYPE_MMS);
526 gst_mms_uri_get_type (GType type)
531 static const gchar *const *
532 gst_mms_uri_get_protocols (GType type)
534 static const gchar *protocols[] = { "mms", "mmsh", "mmst", "mmsu", NULL };
540 gst_mms_uri_get_uri (GstURIHandler * handler)
542 GstMMS *src = GST_MMS (handler);
544 /* FIXME: make thread-safe */
545 return g_strdup (src->uri_name);
549 gst_mms_src_make_valid_uri (const gchar * uri)
552 const gchar *colon, *tmp;
555 if (!uri || !gst_uri_is_valid (uri))
558 protocol = gst_uri_get_protocol (uri);
560 if ((strcmp (protocol, "mms") != 0) && (strcmp (protocol, "mmsh") != 0) &&
561 (strcmp (protocol, "mmst") != 0) && (strcmp (protocol, "mmsu") != 0)) {
567 colon = strstr (uri, "://");
576 /* libmms segfaults if there's no hostname or
577 * no / after the hostname
579 colon = strstr (tmp, "/");
583 if (strstr (tmp, "/") == NULL) {
587 ret = g_malloc0 (len + 2);
588 memcpy (ret, uri, len);
592 return g_strdup (uri);
597 gst_mms_uri_set_uri (GstURIHandler * handler, const gchar * uri,
600 GstMMS *src = GST_MMS (handler);
603 fixed_uri = gst_mms_src_make_valid_uri (uri);
604 if (!fixed_uri && uri) {
605 g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
610 GST_OBJECT_LOCK (src);
611 g_free (src->uri_name);
612 src->uri_name = fixed_uri;
613 GST_OBJECT_UNLOCK (src);
619 gst_mms_uri_handler_init (gpointer g_iface, gpointer iface_data)
621 GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
623 iface->get_type = gst_mms_uri_get_type;
624 iface->get_protocols = gst_mms_uri_get_protocols;
625 iface->get_uri = gst_mms_uri_get_uri;
626 iface->set_uri = gst_mms_uri_set_uri;
630 /* this is the structure that gst-register looks for
631 * so keep the name plugin_desc, or you cannot get your plug-in registered */
632 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
635 "Microsoft Multi Media Server streaming protocol support",
636 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)