Tizen 2.0 Release
[framework/multimedia/gst-plugins-bad0.10.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., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, 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);
72
73 static void
74 gst_mms_urihandler_init (GType mms_type)
75 {
76   static const GInterfaceInfo urihandler_info = {
77     gst_mms_uri_handler_init,
78     NULL,
79     NULL
80   };
81
82   g_type_add_interface_static (mms_type, GST_TYPE_URI_HANDLER,
83       &urihandler_info);
84 }
85
86 GST_BOILERPLATE_FULL (GstMMS, gst_mms, GstPushSrc, GST_TYPE_PUSH_SRC,
87     gst_mms_urihandler_init);
88
89 static void
90 gst_mms_base_init (gpointer g_class)
91 {
92   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
93
94   gst_element_class_add_static_pad_template (element_class, &src_factory);
95   gst_element_class_set_details_simple (element_class, "MMS streaming source",
96       "Source/Network",
97       "Receive data streamed via MSFT Multi Media Server protocol",
98       "Maciej Katafiasz <mathrick@users.sourceforge.net>");
99
100   GST_DEBUG_CATEGORY_INIT (mmssrc_debug, "mmssrc", 0, "MMS Source Element");
101 }
102
103 /* initialize the plugin's class */
104 static void
105 gst_mms_class_init (GstMMSClass * klass)
106 {
107   GObjectClass *gobject_class = (GObjectClass *) klass;
108   GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass;
109   GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass;
110
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;
114
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));
119
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 */
126
127   gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_mms_start);
128   gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_mms_stop);
129
130   gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_mms_create);
131
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);
138 }
139
140 /* initialize the new element
141  * instantiate pads and add them to element
142  * set functions
143  * initialize structure
144  */
145 static void
146 gst_mms_init (GstMMS * mmssrc, GstMMSClass * g_class)
147 {
148   mmssrc->uri_name = NULL;
149   mmssrc->current_connection_uri_name = NULL;
150   mmssrc->connection = NULL;
151   mmssrc->connection_speed = DEFAULT_CONNECTION_SPEED;
152 }
153
154 static void
155 gst_mms_finalize (GObject * gobject)
156 {
157   GstMMS *mmssrc = GST_MMS (gobject);
158
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;
164   }
165
166   if (mmssrc->current_connection_uri_name) {
167     g_free (mmssrc->current_connection_uri_name);
168     mmssrc->current_connection_uri_name = NULL;
169   }
170
171   if (mmssrc->uri_name) {
172     g_free (mmssrc->uri_name);
173     mmssrc->uri_name = NULL;
174   }
175
176   G_OBJECT_CLASS (parent_class)->finalize (gobject);
177 }
178
179 /* FIXME operating in TIME rather than BYTES could remove this altogether
180  * and be more convenient elsewhere */
181 static gboolean
182 gst_mms_query (GstBaseSrc * src, GstQuery * query)
183 {
184   GstMMS *mmssrc = GST_MMS (src);
185   gboolean res = TRUE;
186   GstFormat format;
187   gint64 value;
188
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) {
193         res = FALSE;
194         break;
195       }
196       value = (gint64) mmsx_get_current_pos (mmssrc->connection);
197       gst_query_set_position (query, format, value);
198       break;
199     case GST_QUERY_DURATION:
200       if (!mmsx_get_seekable (mmssrc->connection)) {
201         res = FALSE;
202         break;
203       }
204       gst_query_parse_duration (query, &format, &value);
205       switch (format) {
206         case GST_FORMAT_BYTES:
207           value = (gint64) mmsx_get_length (mmssrc->connection);
208           gst_query_set_duration (query, format, value);
209           break;
210         case GST_FORMAT_TIME:
211           value = mmsx_get_time_length (mmssrc->connection) * GST_SECOND;
212           gst_query_set_duration (query, format, value);
213           break;
214         default:
215           res = FALSE;
216       }
217       break;
218     default:
219       /* chain to parent */
220       res =
221           GST_BASE_SRC_CLASS (parent_class)->query (GST_BASE_SRC (src), query);
222       break;
223   }
224
225   return res;
226 }
227
228
229 static gboolean
230 gst_mms_prepare_seek_segment (GstBaseSrc * src, GstEvent * event,
231     GstSegment * segment)
232 {
233   GstSeekType cur_type, stop_type;
234   gint64 cur, stop;
235   GstSeekFlags flags;
236   GstFormat seek_format;
237   gdouble rate;
238
239   gst_event_parse_seek (event, &rate, &seek_format, &flags,
240       &cur_type, &cur, &stop_type, &stop);
241
242   if (seek_format != GST_FORMAT_BYTES && seek_format != GST_FORMAT_TIME) {
243     GST_LOG_OBJECT (src, "Only byte or time seeking is supported");
244     return FALSE;
245   }
246
247   if (stop_type != GST_SEEK_TYPE_NONE) {
248     GST_LOG_OBJECT (src, "Stop seeking not supported");
249     return FALSE;
250   }
251
252   if (cur_type != GST_SEEK_TYPE_NONE && cur_type != GST_SEEK_TYPE_SET) {
253     GST_LOG_OBJECT (src, "Only absolute seeking is supported");
254     return FALSE;
255   }
256
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. */
260
261   /* FIXME implement relative seeking, we could do any needed relevant
262      seeking calculations here (in seek_format metrics), before the re-init
263      of the segment. */
264
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);
268
269   return TRUE;
270 }
271
272 static gboolean
273 gst_mms_do_seek (GstBaseSrc * src, GstSegment * segment)
274 {
275   gint64 start;
276   GstMMS *mmssrc = GST_MMS (src);
277
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");
282       return FALSE;
283     }
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 */
291     if (start == -1) {
292       GST_DEBUG_OBJECT (mmssrc, "connection broken during seek");
293       return FALSE;
294     }
295     GST_INFO_OBJECT (mmssrc, "sought to: %" G_GINT64_FORMAT " bytes, "
296         "result: %" G_GINT64_FORMAT, segment->start, start);
297   } else {
298     GST_DEBUG_OBJECT (mmssrc, "unsupported seek segment format: %s",
299         GST_STR_NULL (gst_format_get_name (segment->format)));
300     return FALSE;
301   }
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);
306   return TRUE;
307 }
308
309
310 /* get function
311  * this function generates new data when needed
312  */
313
314
315 static GstFlowReturn
316 gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf)
317 {
318   GstMMS *mmssrc = GST_MMS (psrc);
319   guint8 *data;
320   guint blocksize;
321   gint result;
322   mms_off_t offset;
323
324   *buf = NULL;
325
326   offset = mmsx_get_current_pos (mmssrc->connection);
327
328   /* Check if a seek perhaps has wrecked our connection */
329   if (offset == -1) {
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;
333   }
334
335   /* Choose blocksize best for optimum performance */
336   if (offset == 0)
337     blocksize = mmsx_get_asf_header_len (mmssrc->connection);
338   else
339     blocksize = mmsx_get_asf_packet_len (mmssrc->connection);
340
341   *buf = gst_buffer_try_new_and_alloc (blocksize);
342   if (!*buf) {
343     GST_ERROR_OBJECT (mmssrc, "Failed to allocate %u bytes", blocksize);
344     return GST_FLOW_ERROR;
345   }
346
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);
351
352   /* EOS? */
353   if (result == 0)
354     goto eos;
355
356   GST_BUFFER_OFFSET (*buf) = offset;
357   GST_BUFFER_SIZE (*buf) = result;
358
359   GST_LOG_OBJECT (mmssrc, "Returning buffer with offset %" G_GINT64_FORMAT
360       " and size %u", GST_BUFFER_OFFSET (*buf), GST_BUFFER_SIZE (*buf));
361
362   gst_buffer_set_caps (*buf, GST_PAD_CAPS (GST_BASE_SRC_PAD (mmssrc)));
363
364   return GST_FLOW_OK;
365
366 eos:
367   {
368     GST_DEBUG_OBJECT (mmssrc, "EOS");
369     gst_buffer_unref (*buf);
370     *buf = NULL;
371     return GST_FLOW_UNEXPECTED;
372   }
373 }
374
375 static gboolean
376 gst_mms_is_seekable (GstBaseSrc * src)
377 {
378   GstMMS *mmssrc = GST_MMS (src);
379
380   return mmsx_get_seekable (mmssrc->connection);
381 }
382
383 static gboolean
384 gst_mms_get_size (GstBaseSrc * src, guint64 * size)
385 {
386   GstMMS *mmssrc = GST_MMS (src);
387
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))
391     return FALSE;
392
393   *size = mmsx_get_length (mmssrc->connection);
394   return TRUE;
395 }
396
397 static gboolean
398 gst_mms_start (GstBaseSrc * bsrc)
399 {
400   GstMMS *mms = GST_MMS (bsrc);
401   guint bandwidth_avail;
402
403   if (!mms->uri_name || *mms->uri_name == '\0')
404     goto no_uri;
405
406   if (mms->connection_speed)
407     bandwidth_avail = mms->connection_speed;
408   else
409     bandwidth_avail = G_MAXINT;
410
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",
416           mms->uri_name);
417       return TRUE;
418     } else {
419       mmsx_close (mms->connection);
420       g_free (mms->current_connection_uri_name);
421       mms->current_connection_uri_name = NULL;
422     }
423   }
424
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,
432        see above. */
433     mms->current_connection_uri_name = g_strdup (mms->uri_name);
434     GST_DEBUG_OBJECT (mms, "Connect successful");
435     return TRUE;
436   } else {
437     gchar *url, *location;
438
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')
443       goto no_uri;
444     url = g_strdup_printf ("rtsp://%s", location + 3);
445
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,
449                 NULL)));
450
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."));
457
458     return FALSE;
459   }
460
461 no_uri:
462   {
463     GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
464         ("No URI to open specified"), (NULL));
465     return FALSE;
466   }
467 }
468
469 static gboolean
470 gst_mms_stop (GstBaseSrc * bsrc)
471 {
472   GstMMS *mms = GST_MMS (bsrc);
473
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
478        is expensive */
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;
485     }
486   }
487   return TRUE;
488 }
489
490 static void
491 gst_mms_set_property (GObject * object, guint prop_id,
492     const GValue * value, GParamSpec * pspec)
493 {
494   GstMMS *mmssrc = GST_MMS (object);
495
496   switch (prop_id) {
497     case PROP_LOCATION:
498       gst_mms_uri_set_uri (GST_URI_HANDLER (mmssrc),
499           g_value_get_string (value));
500       break;
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);
505       break;
506     default:
507       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
508       break;
509   }
510 }
511
512 static void
513 gst_mms_get_property (GObject * object, guint prop_id,
514     GValue * value, GParamSpec * pspec)
515 {
516   GstMMS *mmssrc = GST_MMS (object);
517
518   GST_OBJECT_LOCK (mmssrc);
519   switch (prop_id) {
520     case PROP_LOCATION:
521       if (mmssrc->uri_name)
522         g_value_set_string (value, mmssrc->uri_name);
523       break;
524     case PROP_CONNECTION_SPEED:
525       g_value_set_uint (value, mmssrc->connection_speed / 1000);
526       break;
527     default:
528       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
529       break;
530   }
531   GST_OBJECT_UNLOCK (mmssrc);
532 }
533
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
538  */
539 static gboolean
540 plugin_init (GstPlugin * plugin)
541 {
542   return gst_element_register (plugin, "mmssrc", GST_RANK_NONE, GST_TYPE_MMS);
543 }
544
545 static GstURIType
546 gst_mms_uri_get_type (void)
547 {
548   return GST_URI_SRC;
549 }
550
551 static gchar **
552 gst_mms_uri_get_protocols (void)
553 {
554   static const gchar *protocols[] = { "mms", "mmsh", "mmst", "mmsu", NULL };
555
556   return (gchar **) protocols;
557 }
558
559 static const gchar *
560 gst_mms_uri_get_uri (GstURIHandler * handler)
561 {
562   GstMMS *src = GST_MMS (handler);
563
564   return src->uri_name;
565 }
566
567 static gchar *
568 gst_mms_src_make_valid_uri (const gchar * uri)
569 {
570   gchar *protocol;
571   const gchar *colon, *tmp;
572   gsize len;
573
574   if (!uri || !gst_uri_is_valid (uri))
575     return NULL;
576
577   protocol = gst_uri_get_protocol (uri);
578
579   if ((strcmp (protocol, "mms") != 0) && (strcmp (protocol, "mmsh") != 0) &&
580       (strcmp (protocol, "mmst") != 0) && (strcmp (protocol, "mmsu") != 0)) {
581     g_free (protocol);
582     return FALSE;
583   }
584   g_free (protocol);
585
586   colon = strstr (uri, "://");
587   if (!colon)
588     return NULL;
589
590   tmp = colon + 3;
591   len = strlen (tmp);
592   if (len == 0)
593     return NULL;
594
595   /* libmms segfaults if there's no hostname or
596    * no / after the hostname
597    */
598   colon = strstr (tmp, "/");
599   if (colon == tmp)
600     return NULL;
601
602   if (strstr (tmp, "/") == NULL) {
603     gchar *ret;
604
605     len = strlen (uri);
606     ret = g_malloc0 (len + 2);
607     memcpy (ret, uri, len);
608     ret[len] = '/';
609     return ret;
610   } else {
611     return g_strdup (uri);
612   }
613 }
614
615 static gboolean
616 gst_mms_uri_set_uri (GstURIHandler * handler, const gchar * uri)
617 {
618   GstMMS *src = GST_MMS (handler);
619   gchar *fixed_uri;
620
621   fixed_uri = gst_mms_src_make_valid_uri (uri);
622   if (!fixed_uri && uri)
623     return FALSE;
624
625   GST_OBJECT_LOCK (src);
626   if (src->uri_name)
627     g_free (src->uri_name);
628   src->uri_name = fixed_uri;
629   GST_OBJECT_UNLOCK (src);
630
631   return TRUE;
632 }
633
634 static void
635 gst_mms_uri_handler_init (gpointer g_iface, gpointer iface_data)
636 {
637   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
638
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;
643 }
644
645
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,
649     GST_VERSION_MINOR,
650     "mms",
651     "Microsoft Multi Media Server streaming protocol support",
652     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)