56243a2b1995e2c06a337d73e923cde6e2e4079f
[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, "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 */
276     if (start == -1) {
277       GST_DEBUG_OBJECT (mmssrc, "connection broken during seek");
278       return FALSE;
279     }
280     GST_INFO_OBJECT (mmssrc, "sought to: %" G_GINT64_FORMAT " bytes, "
281         "result: %" G_GINT64_FORMAT, segment->start, start);
282   } else {
283     GST_DEBUG_OBJECT (mmssrc, "unsupported seek segment format: %s",
284         GST_STR_NULL (gst_format_get_name (segment->format)));
285     return FALSE;
286   }
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);
291   return TRUE;
292 }
293
294
295 /* get function
296  * this function generates new data when needed
297  */
298
299
300 static GstFlowReturn
301 gst_mms_create (GstPushSrc * psrc, GstBuffer ** buf)
302 {
303   GstMMS *mmssrc = GST_MMS (psrc);
304   guint8 *data;
305   guint blocksize;
306   gint result;
307   goffset offset;
308
309   *buf = NULL;
310
311   offset = mmsx_get_current_pos (mmssrc->connection);
312
313   /* Check if a seek perhaps has wrecked our connection */
314   if (offset == -1) {
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;
318   }
319
320   /* Choose blocksize best for optimum performance */
321   if (offset == 0)
322     blocksize = mmsx_get_asf_header_len (mmssrc->connection);
323   else
324     blocksize = mmsx_get_asf_packet_len (mmssrc->connection);
325
326   data = g_try_malloc (blocksize);
327   if (!data) {
328     GST_ERROR_OBJECT (mmssrc, "Failed to allocate %u bytes", blocksize);
329     return GST_FLOW_ERROR;
330   }
331
332   GST_LOG_OBJECT (mmssrc, "reading %d bytes", blocksize);
333   result = mmsx_read (NULL, mmssrc->connection, (char *) data, blocksize);
334   /* EOS? */
335   if (result == 0)
336     goto eos;
337
338   *buf = gst_buffer_new_wrapped (data, result);
339   GST_BUFFER_OFFSET (*buf) = offset;
340
341   GST_LOG_OBJECT (mmssrc, "Returning buffer with offset %" G_GOFFSET_FORMAT
342       " and size %u", offset, result);
343
344   return GST_FLOW_OK;
345
346 eos:
347   {
348     GST_DEBUG_OBJECT (mmssrc, "EOS");
349     g_free (data);
350     *buf = NULL;
351     return GST_FLOW_EOS;
352   }
353 }
354
355 static gboolean
356 gst_mms_is_seekable (GstBaseSrc * src)
357 {
358   GstMMS *mmssrc = GST_MMS (src);
359
360   return mmsx_get_seekable (mmssrc->connection);
361 }
362
363 static gboolean
364 gst_mms_get_size (GstBaseSrc * src, guint64 * size)
365 {
366   GstMMS *mmssrc = GST_MMS (src);
367
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))
371     return FALSE;
372
373   *size = mmsx_get_length (mmssrc->connection);
374   return TRUE;
375 }
376
377 static gboolean
378 gst_mms_start (GstBaseSrc * bsrc)
379 {
380   GstMMS *mms = GST_MMS (bsrc);
381   guint bandwidth_avail;
382
383   if (!mms->uri_name || *mms->uri_name == '\0')
384     goto no_uri;
385
386   if (mms->connection_speed)
387     bandwidth_avail = mms->connection_speed;
388   else
389     bandwidth_avail = G_MAXINT;
390
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",
396           mms->uri_name);
397       return TRUE;
398     } else {
399       mmsx_close (mms->connection);
400       g_free (mms->current_connection_uri_name);
401       mms->current_connection_uri_name = NULL;
402     }
403   }
404
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,
412        see above. */
413     mms->current_connection_uri_name = g_strdup (mms->uri_name);
414     GST_DEBUG_OBJECT (mms, "Connect successful");
415     return TRUE;
416   } else {
417     gchar *url, *location;
418
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')
423       goto no_uri;
424     url = g_strdup_printf ("rtsp://%s", location + 3);
425
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,
429                 NULL)));
430
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."));
437
438     return FALSE;
439   }
440
441 no_uri:
442   {
443     GST_ELEMENT_ERROR (mms, RESOURCE, OPEN_READ,
444         ("No URI to open specified"), (NULL));
445     return FALSE;
446   }
447 }
448
449 static gboolean
450 gst_mms_stop (GstBaseSrc * bsrc)
451 {
452   GstMMS *mms = GST_MMS (bsrc);
453
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
458        is expensive */
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;
465     }
466   }
467   return TRUE;
468 }
469
470 static void
471 gst_mms_set_property (GObject * object, guint prop_id,
472     const GValue * value, GParamSpec * pspec)
473 {
474   GstMMS *mmssrc = GST_MMS (object);
475
476   switch (prop_id) {
477     case PROP_LOCATION:
478       gst_mms_uri_set_uri (GST_URI_HANDLER (mmssrc),
479           g_value_get_string (value), NULL);
480       break;
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);
485       break;
486     default:
487       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
488       break;
489   }
490 }
491
492 static void
493 gst_mms_get_property (GObject * object, guint prop_id,
494     GValue * value, GParamSpec * pspec)
495 {
496   GstMMS *mmssrc = GST_MMS (object);
497
498   GST_OBJECT_LOCK (mmssrc);
499   switch (prop_id) {
500     case PROP_LOCATION:
501       if (mmssrc->uri_name)
502         g_value_set_string (value, mmssrc->uri_name);
503       break;
504     case PROP_CONNECTION_SPEED:
505       g_value_set_uint64 (value, mmssrc->connection_speed / 1000);
506       break;
507     default:
508       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
509       break;
510   }
511   GST_OBJECT_UNLOCK (mmssrc);
512 }
513
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
518  */
519 static gboolean
520 plugin_init (GstPlugin * plugin)
521 {
522   return gst_element_register (plugin, "mmssrc", GST_RANK_NONE, GST_TYPE_MMS);
523 }
524
525 static GstURIType
526 gst_mms_uri_get_type (GType type)
527 {
528   return GST_URI_SRC;
529 }
530
531 static const gchar *const *
532 gst_mms_uri_get_protocols (GType type)
533 {
534   static const gchar *protocols[] = { "mms", "mmsh", "mmst", "mmsu", NULL };
535
536   return protocols;
537 }
538
539 static gchar *
540 gst_mms_uri_get_uri (GstURIHandler * handler)
541 {
542   GstMMS *src = GST_MMS (handler);
543
544   /* FIXME: make thread-safe */
545   return g_strdup (src->uri_name);
546 }
547
548 static gchar *
549 gst_mms_src_make_valid_uri (const gchar * uri)
550 {
551   gchar *protocol;
552   const gchar *colon, *tmp;
553   gsize len;
554
555   if (!uri || !gst_uri_is_valid (uri))
556     return NULL;
557
558   protocol = gst_uri_get_protocol (uri);
559
560   if ((strcmp (protocol, "mms") != 0) && (strcmp (protocol, "mmsh") != 0) &&
561       (strcmp (protocol, "mmst") != 0) && (strcmp (protocol, "mmsu") != 0)) {
562     g_free (protocol);
563     return FALSE;
564   }
565   g_free (protocol);
566
567   colon = strstr (uri, "://");
568   if (!colon)
569     return NULL;
570
571   tmp = colon + 3;
572   len = strlen (tmp);
573   if (len == 0)
574     return NULL;
575
576   /* libmms segfaults if there's no hostname or
577    * no / after the hostname
578    */
579   colon = strstr (tmp, "/");
580   if (colon == tmp)
581     return NULL;
582
583   if (strstr (tmp, "/") == NULL) {
584     gchar *ret;
585
586     len = strlen (uri);
587     ret = g_malloc0 (len + 2);
588     memcpy (ret, uri, len);
589     ret[len] = '/';
590     return ret;
591   } else {
592     return g_strdup (uri);
593   }
594 }
595
596 static gboolean
597 gst_mms_uri_set_uri (GstURIHandler * handler, const gchar * uri,
598     GError ** error)
599 {
600   GstMMS *src = GST_MMS (handler);
601   gchar *fixed_uri;
602
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,
606         "Invalid MMS URI");
607     return FALSE;
608   }
609
610   GST_OBJECT_LOCK (src);
611   g_free (src->uri_name);
612   src->uri_name = fixed_uri;
613   GST_OBJECT_UNLOCK (src);
614
615   return TRUE;
616 }
617
618 static void
619 gst_mms_uri_handler_init (gpointer g_iface, gpointer iface_data)
620 {
621   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
622
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;
627 }
628
629
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,
633     GST_VERSION_MINOR,
634     mms,
635     "Microsoft Multi Media Server streaming protocol support",
636     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)