documentation: fixed a heap o' typos
[platform/upstream/gstreamer.git] / ext / libmms / gstmms.c
1 /*
2  *
3  * GStreamer
4  * Copyright (C) 1999-2001 Erik Walthinsen <omega@cse.ogi.edu>
5  *
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.
10  *
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.
15  *
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.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25
26 #include <gst/gst.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include "gstmms.h"
30
31 #define DEFAULT_CONNECTION_SPEED    0
32
33 enum
34 {
35   PROP_0,
36   PROP_LOCATION,
37   PROP_CONNECTION_SPEED
38 };
39
40
41 GST_DEBUG_CATEGORY_STATIC (mmssrc_debug);
42 #define GST_CAT_DEFAULT mmssrc_debug
43
44 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
45     GST_PAD_SRC,
46     GST_PAD_ALWAYS,
47     GST_STATIC_CAPS ("video/x-ms-asf")
48     );
49
50 static void gst_mms_finalize (GObject * gobject);
51 static void gst_mms_uri_handler_init (gpointer g_iface, gpointer iface_data);
52
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);
57
58 static gboolean gst_mms_query (GstBaseSrc * src, GstQuery * query);
59
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);
67
68 static GstFlowReturn gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf);
69
70 static gboolean gst_mms_uri_set_uri (GstURIHandler * handler,
71     const gchar * uri, GError ** error);
72
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
77 /* initialize the plugin's class */
78 static void
79 gst_mms_class_init (GstMMSClass * klass)
80 {
81   GObjectClass *gobject_class = (GObjectClass *) klass;
82   GstElementClass *gstelement_class = (GstElementClass *) klass;
83   GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass;
84   GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass;
85
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;
89
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));
94
95   /* Note: connection-speed is intentionaly limited to G_MAXINT as libmms
96    * uses int for it */
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));
102
103   gst_element_class_add_static_pad_template (gstelement_class, &src_factory);
104
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>");
109
110   GST_DEBUG_CATEGORY_INIT (mmssrc_debug, "mmssrc", 0, "MMS Source Element");
111
112   gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_mms_start);
113   gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_mms_stop);
114
115   gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_mms_create);
116
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);
123 }
124
125 /* initialize the new element
126  * instantiate pads and add them to element
127  * set functions
128  * initialize structure
129  */
130 static void
131 gst_mms_init (GstMMS * mmssrc)
132 {
133   mmssrc->uri_name = NULL;
134   mmssrc->current_connection_uri_name = NULL;
135   mmssrc->connection = NULL;
136   mmssrc->connection_speed = DEFAULT_CONNECTION_SPEED;
137 }
138
139 static void
140 gst_mms_finalize (GObject * gobject)
141 {
142   GstMMS *mmssrc = GST_MMS (gobject);
143
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;
149   }
150
151   if (mmssrc->current_connection_uri_name) {
152     g_free (mmssrc->current_connection_uri_name);
153     mmssrc->current_connection_uri_name = NULL;
154   }
155
156   if (mmssrc->uri_name) {
157     g_free (mmssrc->uri_name);
158     mmssrc->uri_name = NULL;
159   }
160
161   G_OBJECT_CLASS (parent_class)->finalize (gobject);
162 }
163
164 /* FIXME operating in TIME rather than BYTES could remove this altogether
165  * and be more convenient elsewhere */
166 static gboolean
167 gst_mms_query (GstBaseSrc * src, GstQuery * query)
168 {
169   GstMMS *mmssrc = GST_MMS (src);
170   gboolean res = TRUE;
171   GstFormat format;
172   gint64 value;
173
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) {
178         res = FALSE;
179         break;
180       }
181       value = (gint64) mmsx_get_current_pos (mmssrc->connection);
182       gst_query_set_position (query, format, value);
183       break;
184     case GST_QUERY_DURATION:
185       if (!mmsx_get_seekable (mmssrc->connection)) {
186         res = FALSE;
187         break;
188       }
189       gst_query_parse_duration (query, &format, &value);
190       switch (format) {
191         case GST_FORMAT_BYTES:
192           value = (gint64) mmsx_get_length (mmssrc->connection);
193           gst_query_set_duration (query, format, value);
194           break;
195         case GST_FORMAT_TIME:
196           value = mmsx_get_time_length (mmssrc->connection) * GST_SECOND;
197           gst_query_set_duration (query, format, value);
198           break;
199         default:
200           res = FALSE;
201       }
202       break;
203     default:
204       /* chain to parent */
205       res =
206           GST_BASE_SRC_CLASS (parent_class)->query (GST_BASE_SRC (src), query);
207       break;
208   }
209
210   return res;
211 }
212
213
214 static gboolean
215 gst_mms_prepare_seek_segment (GstBaseSrc * src, GstEvent * event,
216     GstSegment * segment)
217 {
218   GstSeekType cur_type, stop_type;
219   gint64 cur, stop;
220   GstSeekFlags flags;
221   GstFormat seek_format;
222   gdouble rate;
223
224   gst_event_parse_seek (event, &rate, &seek_format, &flags,
225       &cur_type, &cur, &stop_type, &stop);
226
227   if (seek_format != GST_FORMAT_BYTES && seek_format != GST_FORMAT_TIME) {
228     GST_LOG_OBJECT (src, "Only byte or time seeking is supported");
229     return FALSE;
230   }
231
232   if (stop_type != GST_SEEK_TYPE_NONE) {
233     GST_LOG_OBJECT (src, "Stop seeking not supported");
234     return FALSE;
235   }
236
237   if (cur_type != GST_SEEK_TYPE_NONE && cur_type != GST_SEEK_TYPE_SET) {
238     GST_LOG_OBJECT (src, "Only absolute seeking is supported");
239     return FALSE;
240   }
241
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. */
245
246   /* FIXME implement relative seeking, we could do any needed relevant
247      seeking calculations here (in seek_format metrics), before the re-init
248      of the segment. */
249
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);
253
254   return TRUE;
255 }
256
257 static gboolean
258 gst_mms_do_seek (GstBaseSrc * src, GstSegment * segment)
259 {
260   gint64 start;
261   GstMMS *mmssrc = GST_MMS (src);
262
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");
267       return FALSE;
268     }
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 */
277     if (start == -1) {
278       GST_DEBUG_OBJECT (mmssrc, "connection broken during seek");
279       return FALSE;
280     }
281     GST_INFO_OBJECT (mmssrc, "performed seek to: %" G_GINT64_FORMAT " bytes, "
282         "result: %" G_GINT64_FORMAT, segment->start, start);
283   } else {
284     GST_DEBUG_OBJECT (mmssrc, "unsupported seek segment format: %s",
285         GST_STR_NULL (gst_format_get_name (segment->format)));
286     return FALSE;
287   }
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);
292   return TRUE;
293 }
294
295
296 /* get function
297  * this function generates new data when needed
298  */
299
300
301 static GstFlowReturn
302 gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf)
303 {
304   GstMMS *mmssrc = GST_MMS (psrc);
305   guint8 *data;
306   guint blocksize;
307   gint result;
308   goffset offset;
309
310   *buf = NULL;
311
312   offset = mmsx_get_current_pos (mmssrc->connection);
313
314   /* Check if a seek perhaps has wrecked our connection */
315   if (offset == -1) {
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;
319   }
320
321   /* Choose blocksize best for optimum performance */
322   if (offset == 0)
323     blocksize = mmsx_get_asf_header_len (mmssrc->connection);
324   else
325     blocksize = mmsx_get_asf_packet_len (mmssrc->connection);
326
327   data = g_try_malloc (blocksize);
328   if (!data) {
329     GST_ERROR_OBJECT (mmssrc, "Failed to allocate %u bytes", blocksize);
330     return GST_FLOW_ERROR;
331   }
332
333   GST_LOG_OBJECT (mmssrc, "reading %d bytes", blocksize);
334   result = mmsx_read (NULL, mmssrc->connection, (char *) data, blocksize);
335   /* EOS? */
336   if (result == 0)
337     goto eos;
338
339   *buf = gst_buffer_new_wrapped (data, result);
340   GST_BUFFER_OFFSET (*buf) = offset;
341
342   GST_LOG_OBJECT (mmssrc, "Returning buffer with offset %" G_GOFFSET_FORMAT
343       " and size %u", offset, result);
344
345   return GST_FLOW_OK;
346
347 eos:
348   {
349     GST_DEBUG_OBJECT (mmssrc, "EOS");
350     g_free (data);
351     *buf = NULL;
352     return GST_FLOW_EOS;
353   }
354 }
355
356 static gboolean
357 gst_mms_is_seekable (GstBaseSrc * src)
358 {
359   GstMMS *mmssrc = GST_MMS (src);
360
361   return mmsx_get_seekable (mmssrc->connection);
362 }
363
364 static gboolean
365 gst_mms_get_size (GstBaseSrc * src, guint64 * size)
366 {
367   GstMMS *mmssrc = GST_MMS (src);
368
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))
372     return FALSE;
373
374   *size = mmsx_get_length (mmssrc->connection);
375   return TRUE;
376 }
377
378 static gboolean
379 gst_mms_start (GstBaseSrc * bsrc)
380 {
381   GstMMS *mms = GST_MMS (bsrc);
382   guint bandwidth_avail;
383
384   if (!mms->uri_name || *mms->uri_name == '\0')
385     goto no_uri;
386
387   if (mms->connection_speed)
388     bandwidth_avail = mms->connection_speed;
389   else
390     bandwidth_avail = G_MAXINT;
391
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",
397           mms->uri_name);
398       return TRUE;
399     } else {
400       mmsx_close (mms->connection);
401       g_free (mms->current_connection_uri_name);
402       mms->current_connection_uri_name = NULL;
403     }
404   }
405
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,
413        see above. */
414     mms->current_connection_uri_name = g_strdup (mms->uri_name);
415     GST_DEBUG_OBJECT (mms, "Connect successful");
416     return TRUE;
417   } else {
418     gchar *url, *location;
419
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')
424       goto no_uri;
425     url = g_strdup_printf ("rtsp://%s", location + 3);
426
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,
430                 NULL)));
431
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."));
438
439     return FALSE;
440   }
441
442 no_uri:
443   {
444     GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
445         ("No URI to open specified"), (NULL));
446     return FALSE;
447   }
448 }
449
450 static gboolean
451 gst_mms_stop (GstBaseSrc * bsrc)
452 {
453   GstMMS *mms = GST_MMS (bsrc);
454
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
459        is expensive */
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;
466     }
467   }
468   return TRUE;
469 }
470
471 static void
472 gst_mms_set_property (GObject * object, guint prop_id,
473     const GValue * value, GParamSpec * pspec)
474 {
475   GstMMS *mmssrc = GST_MMS (object);
476
477   switch (prop_id) {
478     case PROP_LOCATION:
479       gst_mms_uri_set_uri (GST_URI_HANDLER (mmssrc),
480           g_value_get_string (value), NULL);
481       break;
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);
486       break;
487     default:
488       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
489       break;
490   }
491 }
492
493 static void
494 gst_mms_get_property (GObject * object, guint prop_id,
495     GValue * value, GParamSpec * pspec)
496 {
497   GstMMS *mmssrc = GST_MMS (object);
498
499   GST_OBJECT_LOCK (mmssrc);
500   switch (prop_id) {
501     case PROP_LOCATION:
502       if (mmssrc->uri_name)
503         g_value_set_string (value, mmssrc->uri_name);
504       break;
505     case PROP_CONNECTION_SPEED:
506       g_value_set_uint64 (value, mmssrc->connection_speed / 1000);
507       break;
508     default:
509       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
510       break;
511   }
512   GST_OBJECT_UNLOCK (mmssrc);
513 }
514
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
519  */
520 static gboolean
521 plugin_init (GstPlugin * plugin)
522 {
523   return gst_element_register (plugin, "mmssrc", GST_RANK_NONE, GST_TYPE_MMS);
524 }
525
526 static GstURIType
527 gst_mms_uri_get_type (GType type)
528 {
529   return GST_URI_SRC;
530 }
531
532 static const gchar *const *
533 gst_mms_uri_get_protocols (GType type)
534 {
535   static const gchar *protocols[] = { "mms", "mmsh", "mmst", "mmsu", NULL };
536
537   return protocols;
538 }
539
540 static gchar *
541 gst_mms_uri_get_uri (GstURIHandler * handler)
542 {
543   GstMMS *src = GST_MMS (handler);
544
545   /* FIXME: make thread-safe */
546   return g_strdup (src->uri_name);
547 }
548
549 static gchar *
550 gst_mms_src_make_valid_uri (const gchar * uri)
551 {
552   gchar *protocol;
553   const gchar *colon, *tmp;
554   gsize len;
555
556   if (!uri || !gst_uri_is_valid (uri))
557     return NULL;
558
559   protocol = gst_uri_get_protocol (uri);
560
561   if ((strcmp (protocol, "mms") != 0) && (strcmp (protocol, "mmsh") != 0) &&
562       (strcmp (protocol, "mmst") != 0) && (strcmp (protocol, "mmsu") != 0)) {
563     g_free (protocol);
564     return FALSE;
565   }
566   g_free (protocol);
567
568   colon = strstr (uri, "://");
569   if (!colon)
570     return NULL;
571
572   tmp = colon + 3;
573   len = strlen (tmp);
574   if (len == 0)
575     return NULL;
576
577   /* libmms segfaults if there's no hostname or
578    * no / after the hostname
579    */
580   colon = strstr (tmp, "/");
581   if (colon == tmp)
582     return NULL;
583
584   if (strstr (tmp, "/") == NULL) {
585     gchar *ret;
586
587     len = strlen (uri);
588     ret = g_malloc0 (len + 2);
589     memcpy (ret, uri, len);
590     ret[len] = '/';
591     return ret;
592   } else {
593     return g_strdup (uri);
594   }
595 }
596
597 static gboolean
598 gst_mms_uri_set_uri (GstURIHandler * handler, const gchar * uri,
599     GError ** error)
600 {
601   GstMMS *src = GST_MMS (handler);
602   gchar *fixed_uri;
603
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,
607         "Invalid MMS URI");
608     return FALSE;
609   }
610
611   GST_OBJECT_LOCK (src);
612   g_free (src->uri_name);
613   src->uri_name = fixed_uri;
614   GST_OBJECT_UNLOCK (src);
615
616   return TRUE;
617 }
618
619 static void
620 gst_mms_uri_handler_init (gpointer g_iface, gpointer iface_data)
621 {
622   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
623
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;
628 }
629
630
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,
634     GST_VERSION_MINOR,
635     mms,
636     "Microsoft Multi Media Server streaming protocol support",
637     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)