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