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));
76 GST_ELEMENT_REGISTER_DEFINE (mmssrc, "mmssrc", GST_RANK_NONE, GST_TYPE_MMS);
78 /* initialize the plugin's class */
80 gst_mms_class_init (GstMMSClass * klass)
82 GObjectClass *gobject_class = (GObjectClass *) klass;
83 GstElementClass *gstelement_class = (GstElementClass *) klass;
84 GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass;
85 GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass;
87 gobject_class->set_property = gst_mms_set_property;
88 gobject_class->get_property = gst_mms_get_property;
89 gobject_class->finalize = gst_mms_finalize;
91 g_object_class_install_property (gobject_class, PROP_LOCATION,
92 g_param_spec_string ("location", "location",
93 "Host URL to connect to. Accepted are mms://, mmsu://, mmst:// URL types",
94 NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
96 /* Note: connection-speed is intentionaly limited to G_MAXINT as libmms
98 g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED,
99 g_param_spec_uint64 ("connection-speed", "Connection Speed",
100 "Network connection speed in kbps (0 = unknown)",
101 0, G_MAXINT / 1000, DEFAULT_CONNECTION_SPEED,
102 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
104 gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
106 gst_element_class_set_static_metadata (gstelement_class,
107 "MMS streaming source", "Source/Network",
108 "Receive data streamed via MSFT Multi Media Server protocol",
109 "Maciej Katafiasz <mathrick@users.sourceforge.net>");
111 GST_DEBUG_CATEGORY_INIT (mmssrc_debug, "mmssrc", 0, "MMS Source Element");
113 gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_mms_start);
114 gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_mms_stop);
116 gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_mms_create);
118 gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_mms_is_seekable);
119 gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_mms_get_size);
120 gstbasesrc_class->prepare_seek_segment =
121 GST_DEBUG_FUNCPTR (gst_mms_prepare_seek_segment);
122 gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_mms_do_seek);
123 gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_mms_query);
126 /* initialize the new element
127 * instantiate pads and add them to element
129 * initialize structure
132 gst_mms_init (GstMMS * mmssrc)
134 mmssrc->uri_name = NULL;
135 mmssrc->current_connection_uri_name = NULL;
136 mmssrc->connection = NULL;
137 mmssrc->connection_speed = DEFAULT_CONNECTION_SPEED;
141 gst_mms_finalize (GObject * gobject)
143 GstMMS *mmssrc = GST_MMS (gobject);
145 /* We may still have a connection open, as we preserve unused / pristine
146 open connections in stop to reuse them in start. */
147 if (mmssrc->connection) {
148 mmsx_close (mmssrc->connection);
149 mmssrc->connection = NULL;
152 if (mmssrc->current_connection_uri_name) {
153 g_free (mmssrc->current_connection_uri_name);
154 mmssrc->current_connection_uri_name = NULL;
157 if (mmssrc->uri_name) {
158 g_free (mmssrc->uri_name);
159 mmssrc->uri_name = NULL;
162 G_OBJECT_CLASS (parent_class)->finalize (gobject);
165 /* FIXME operating in TIME rather than BYTES could remove this altogether
166 * and be more convenient elsewhere */
168 gst_mms_query (GstBaseSrc * src, GstQuery * query)
170 GstMMS *mmssrc = GST_MMS (src);
175 switch (GST_QUERY_TYPE (query)) {
176 case GST_QUERY_POSITION:
177 gst_query_parse_position (query, &format, &value);
178 if (format != GST_FORMAT_BYTES) {
182 value = (gint64) mmsx_get_current_pos (mmssrc->connection);
183 gst_query_set_position (query, format, value);
185 case GST_QUERY_DURATION:
186 if (!mmsx_get_seekable (mmssrc->connection)) {
190 gst_query_parse_duration (query, &format, &value);
192 case GST_FORMAT_BYTES:
193 value = (gint64) mmsx_get_length (mmssrc->connection);
194 gst_query_set_duration (query, format, value);
196 case GST_FORMAT_TIME:
197 value = mmsx_get_time_length (mmssrc->connection) * GST_SECOND;
198 gst_query_set_duration (query, format, value);
205 /* chain to parent */
207 GST_BASE_SRC_CLASS (parent_class)->query (GST_BASE_SRC (src), query);
216 gst_mms_prepare_seek_segment (GstBaseSrc * src, GstEvent * event,
217 GstSegment * segment)
219 GstSeekType cur_type, stop_type;
222 GstFormat seek_format;
225 gst_event_parse_seek (event, &rate, &seek_format, &flags,
226 &cur_type, &cur, &stop_type, &stop);
228 if (seek_format != GST_FORMAT_BYTES && seek_format != GST_FORMAT_TIME) {
229 GST_LOG_OBJECT (src, "Only byte or time seeking is supported");
233 if (stop_type != GST_SEEK_TYPE_NONE) {
234 GST_LOG_OBJECT (src, "Stop seeking not supported");
238 if (cur_type != GST_SEEK_TYPE_NONE && cur_type != GST_SEEK_TYPE_SET) {
239 GST_LOG_OBJECT (src, "Only absolute seeking is supported");
243 /* We would like to convert from GST_FORMAT_TIME to GST_FORMAT_BYTES here
244 when needed, but we cannot as to do that we need to actually do the seek,
245 so we handle this in do_seek instead. */
247 /* FIXME implement relative seeking, we could do any needed relevant
248 seeking calculations here (in seek_format metrics), before the re-init
251 gst_segment_init (segment, seek_format);
252 gst_segment_do_seek (segment, rate, seek_format, flags, cur_type, cur,
253 stop_type, stop, NULL);
259 gst_mms_do_seek (GstBaseSrc * src, GstSegment * segment)
262 GstMMS *mmssrc = GST_MMS (src);
264 if (segment->format == GST_FORMAT_TIME) {
265 if (!mmsx_time_seek (NULL, mmssrc->connection,
266 (double) segment->start / GST_SECOND)) {
267 GST_LOG_OBJECT (mmssrc, "mmsx_time_seek() failed");
270 start = mmsx_get_current_pos (mmssrc->connection);
271 GST_INFO_OBJECT (mmssrc,
272 "performed seek to %" GST_TIME_FORMAT ", offset after " "seek: %"
273 G_GINT64_FORMAT, GST_TIME_ARGS (segment->start), start);
274 } else if (segment->format == GST_FORMAT_BYTES) {
275 start = mmsx_seek (NULL, mmssrc->connection, segment->start, SEEK_SET);
276 /* mmsx_seek will close and reopen the connection when seeking with the
277 mmsh protocol, if the reopening fails this is indicated with -1 */
279 GST_DEBUG_OBJECT (mmssrc, "connection broken during seek");
282 GST_INFO_OBJECT (mmssrc, "performed seek to: %" G_GINT64_FORMAT " bytes, "
283 "result: %" G_GINT64_FORMAT, segment->start, start);
285 GST_DEBUG_OBJECT (mmssrc, "unsupported seek segment format: %s",
286 GST_STR_NULL (gst_format_get_name (segment->format)));
289 gst_segment_init (segment, GST_FORMAT_BYTES);
290 gst_segment_do_seek (segment, segment->rate, GST_FORMAT_BYTES,
291 GST_SEEK_FLAG_NONE, GST_SEEK_TYPE_SET, start, GST_SEEK_TYPE_NONE,
292 segment->stop, NULL);
298 * this function generates new data when needed
303 gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf)
305 GstMMS *mmssrc = GST_MMS (psrc);
313 offset = mmsx_get_current_pos (mmssrc->connection);
315 /* Check if a seek perhaps has wrecked our connection */
317 GST_ERROR_OBJECT (mmssrc,
318 "connection broken (probably an error during mmsx_seek_time during a convert query) returning FLOW_ERROR");
319 return GST_FLOW_ERROR;
322 /* Choose blocksize best for optimum performance */
324 blocksize = mmsx_get_asf_header_len (mmssrc->connection);
326 blocksize = mmsx_get_asf_packet_len (mmssrc->connection);
328 data = g_try_malloc (blocksize);
330 GST_ERROR_OBJECT (mmssrc, "Failed to allocate %u bytes", blocksize);
331 return GST_FLOW_ERROR;
334 GST_LOG_OBJECT (mmssrc, "reading %d bytes", blocksize);
335 result = mmsx_read (NULL, mmssrc->connection, (char *) data, blocksize);
340 *buf = gst_buffer_new_wrapped (data, result);
341 GST_BUFFER_OFFSET (*buf) = offset;
343 GST_LOG_OBJECT (mmssrc, "Returning buffer with offset %" G_GOFFSET_FORMAT
344 " and size %u", offset, result);
350 GST_DEBUG_OBJECT (mmssrc, "EOS");
358 gst_mms_is_seekable (GstBaseSrc * src)
360 GstMMS *mmssrc = GST_MMS (src);
362 return mmsx_get_seekable (mmssrc->connection);
366 gst_mms_get_size (GstBaseSrc * src, guint64 * size)
368 GstMMS *mmssrc = GST_MMS (src);
370 /* non seekable usually means live streams, and get_length() returns,
371 erm, interesting values for live streams */
372 if (!mmsx_get_seekable (mmssrc->connection))
375 *size = mmsx_get_length (mmssrc->connection);
380 gst_mms_start (GstBaseSrc * bsrc)
382 GstMMS *mms = GST_MMS (bsrc);
383 guint bandwidth_avail;
385 if (!mms->uri_name || *mms->uri_name == '\0')
388 if (mms->connection_speed)
389 bandwidth_avail = mms->connection_speed;
391 bandwidth_avail = G_MAXINT;
393 /* If we already have a connection, and the uri isn't changed, reuse it,
394 as connecting is expensive. */
395 if (mms->connection) {
396 if (!strcmp (mms->uri_name, mms->current_connection_uri_name)) {
397 GST_DEBUG_OBJECT (mms, "Reusing existing connection for %s",
401 mmsx_close (mms->connection);
402 g_free (mms->current_connection_uri_name);
403 mms->current_connection_uri_name = NULL;
407 /* FIXME: pass some sane arguments here */
408 GST_DEBUG_OBJECT (mms,
409 "Trying mms_connect (%s) with bandwidth constraint of %d bps",
410 mms->uri_name, bandwidth_avail);
411 mms->connection = mmsx_connect (NULL, NULL, mms->uri_name, bandwidth_avail);
412 if (mms->connection) {
413 /* Save the uri name so that it can be checked for connection reusing,
415 mms->current_connection_uri_name = g_strdup (mms->uri_name);
416 GST_DEBUG_OBJECT (mms, "Connect successful");
419 gchar *url, *location;
421 GST_ERROR_OBJECT (mms,
422 "Could not connect to this stream, redirecting to rtsp");
423 location = strstr (mms->uri_name, "://");
424 if (location == NULL || *location == '\0' || *(location + 3) == '\0')
426 url = g_strdup_printf ("rtsp://%s", location + 3);
428 gst_element_post_message (GST_ELEMENT_CAST (mms),
429 gst_message_new_element (GST_OBJECT_CAST (mms),
430 gst_structure_new ("redirect", "new-location", G_TYPE_STRING, url,
433 /* post an error message as well, so that applications that don't handle
434 * redirect messages get to see a proper error message */
435 GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
436 ("Could not connect to streaming server."),
437 ("A redirect message was posted on the bus and should have been "
438 "handled by the application."));
445 GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
446 ("No URI to open specified"), (NULL));
452 gst_mms_stop (GstBaseSrc * bsrc)
454 GstMMS *mms = GST_MMS (bsrc);
456 if (mms->connection != NULL) {
457 /* Check if the connection is still pristine, that is if no more then
458 just the mmslib cached asf header has been read. If it is still pristine
459 preserve it as we often are re-started with the same URL and connecting
461 if (mmsx_get_current_pos (mms->connection) >
462 mmsx_get_asf_header_len (mms->connection)) {
463 mmsx_close (mms->connection);
464 mms->connection = NULL;
465 g_free (mms->current_connection_uri_name);
466 mms->current_connection_uri_name = NULL;
473 gst_mms_set_property (GObject * object, guint prop_id,
474 const GValue * value, GParamSpec * pspec)
476 GstMMS *mmssrc = GST_MMS (object);
480 gst_mms_uri_set_uri (GST_URI_HANDLER (mmssrc),
481 g_value_get_string (value), NULL);
483 case PROP_CONNECTION_SPEED:
484 GST_OBJECT_LOCK (mmssrc);
485 mmssrc->connection_speed = g_value_get_uint64 (value) * 1000;
486 GST_OBJECT_UNLOCK (mmssrc);
489 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
495 gst_mms_get_property (GObject * object, guint prop_id,
496 GValue * value, GParamSpec * pspec)
498 GstMMS *mmssrc = GST_MMS (object);
500 GST_OBJECT_LOCK (mmssrc);
503 if (mmssrc->uri_name)
504 g_value_set_string (value, mmssrc->uri_name);
506 case PROP_CONNECTION_SPEED:
507 g_value_set_uint64 (value, mmssrc->connection_speed / 1000);
510 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
513 GST_OBJECT_UNLOCK (mmssrc);
516 /* entry point to initialize the plug-in
517 * initialize the plug-in itself
518 * register the element factories and pad templates
519 * register the features
522 plugin_init (GstPlugin * plugin)
524 return GST_ELEMENT_REGISTER (mmssrc, plugin);
528 gst_mms_uri_get_type (GType type)
533 static const gchar *const *
534 gst_mms_uri_get_protocols (GType type)
536 static const gchar *protocols[] = { "mms", "mmsh", "mmst", "mmsu", NULL };
542 gst_mms_uri_get_uri (GstURIHandler * handler)
544 GstMMS *src = GST_MMS (handler);
546 /* FIXME: make thread-safe */
547 return g_strdup (src->uri_name);
551 gst_mms_src_make_valid_uri (const gchar * uri)
554 const gchar *colon, *tmp;
557 if (!uri || !gst_uri_is_valid (uri))
560 protocol = gst_uri_get_protocol (uri);
562 if ((strcmp (protocol, "mms") != 0) && (strcmp (protocol, "mmsh") != 0) &&
563 (strcmp (protocol, "mmst") != 0) && (strcmp (protocol, "mmsu") != 0)) {
569 colon = strstr (uri, "://");
578 /* libmms segfaults if there's no hostname or
579 * no / after the hostname
581 colon = strstr (tmp, "/");
585 if (strstr (tmp, "/") == NULL) {
589 ret = g_malloc0 (len + 2);
590 memcpy (ret, uri, len);
594 return g_strdup (uri);
599 gst_mms_uri_set_uri (GstURIHandler * handler, const gchar * uri,
602 GstMMS *src = GST_MMS (handler);
605 fixed_uri = gst_mms_src_make_valid_uri (uri);
606 if (!fixed_uri && uri) {
607 g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
612 GST_OBJECT_LOCK (src);
613 g_free (src->uri_name);
614 src->uri_name = fixed_uri;
615 GST_OBJECT_UNLOCK (src);
621 gst_mms_uri_handler_init (gpointer g_iface, gpointer iface_data)
623 GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
625 iface->get_type = gst_mms_uri_get_type;
626 iface->get_protocols = gst_mms_uri_get_protocols;
627 iface->get_uri = gst_mms_uri_get_uri;
628 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)