4844c056a5a3b60da249935cee50338c12fe7db9
[platform/upstream/gstreamer.git] / ext / smoothstreaming / gstmssdemux.c
1 /* GStreamer
2  * Copyright (C) 2012 Smart TV Alliance
3  *  Author: Thiago Sousa Santos <thiago.sousa.santos@collabora.com>, Collabora Ltd.
4  *
5  * gstmssdemux.c:
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 /**
24  * SECTION:element-mssdemux
25  *
26  * Demuxes a Microsoft's Smooth Streaming manifest into its audio and/or video streams.
27  *
28  * TODO
29  */
30
31 #ifdef HAVE_CONFIG_H
32 #include "config.h"
33 #endif
34
35 #include "gst/gst-i18n-plugin.h"
36
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40
41 #include "gstmssdemux.h"
42
43 GST_DEBUG_CATEGORY (mssdemux_debug);
44
45 static GstStaticPadTemplate gst_mss_demux_sink_template =
46 GST_STATIC_PAD_TEMPLATE ("sink",
47     GST_PAD_SINK,
48     GST_PAD_ALWAYS,
49     GST_STATIC_CAPS ("application/vnd.ms-sstr+xml")
50     );
51
52 static GstStaticPadTemplate gst_mss_demux_videosrc_template =
53 GST_STATIC_PAD_TEMPLATE ("video_%02u",
54     GST_PAD_SRC,
55     GST_PAD_SOMETIMES,
56     GST_STATIC_CAPS_ANY);
57
58 static GstStaticPadTemplate gst_mss_demux_audiosrc_template =
59 GST_STATIC_PAD_TEMPLATE ("audio_%02u",
60     GST_PAD_SRC,
61     GST_PAD_SOMETIMES,
62     GST_STATIC_CAPS_ANY);
63
64 GST_BOILERPLATE (GstMssDemux, gst_mss_demux, GstMssDemux, GST_TYPE_ELEMENT);
65
66 static void gst_mss_demux_dispose (GObject * object);
67 static GstStateChangeReturn
68 gst_mss_demux_change_state (GstElement * element, GstStateChange transition);
69 static GstFlowReturn gst_mss_demux_chain (GstPad * pad, GstBuffer * buffer);
70 static GstFlowReturn gst_mss_demux_event (GstPad * pad, GstEvent * event);
71
72 static void gst_mss_demux_stream_loop (GstMssDemuxStream * stream);
73
74 static void gst_mss_demux_process_manifest (GstMssDemux * mssdemux);
75
76 static void
77 gst_mss_demux_base_init (gpointer klass)
78 {
79   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
80
81   gst_element_class_add_static_pad_template (element_class,
82       &gst_mss_demux_sink_template);
83   gst_element_class_add_static_pad_template (element_class,
84       &gst_mss_demux_videosrc_template);
85   gst_element_class_add_static_pad_template (element_class,
86       &gst_mss_demux_audiosrc_template);
87   gst_element_class_set_details_simple (element_class, "Smooth Streaming "
88       "demuxer", "Demuxer",
89       "Parse and demultiplex a Smooth Streaming manifest into audio and video "
90       "streams", "Thiago Santos <thiago.sousa.santos@collabora.com>");
91
92   GST_DEBUG_CATEGORY_INIT (mssdemux_debug, "mssdemux", 0, "mssdemux plugin");
93 }
94
95 static void
96 gst_mss_demux_class_init (GstMssDemuxClass * klass)
97 {
98   GObjectClass *gobject_class;
99   GstElementClass *gstelement_class;
100
101   gobject_class = (GObjectClass *) klass;
102   gstelement_class = (GstElementClass *) klass;
103
104   parent_class = g_type_class_peek_parent (klass);
105
106   gobject_class->dispose = gst_mss_demux_dispose;
107
108   gstelement_class->change_state =
109       GST_DEBUG_FUNCPTR (gst_mss_demux_change_state);
110 }
111
112 static void
113 gst_mss_demux_init (GstMssDemux * mssdemux, GstMssDemuxClass * klass)
114 {
115   mssdemux->sinkpad =
116       gst_pad_new_from_static_template (&gst_mss_demux_sink_template, "sink");
117   gst_pad_set_chain_function (mssdemux->sinkpad,
118       GST_DEBUG_FUNCPTR (gst_mss_demux_chain));
119   gst_pad_set_event_function (mssdemux->sinkpad,
120       GST_DEBUG_FUNCPTR (gst_mss_demux_event));
121   gst_element_add_pad (GST_ELEMENT_CAST (mssdemux), mssdemux->sinkpad);
122 }
123
124 static GstMssDemuxStream *
125 gst_mss_demux_stream_new (GstMssDemux * mssdemux,
126     GstMssStream * manifeststream, GstPad * srcpad)
127 {
128   GstMssDemuxStream *stream;
129
130   stream = g_new0 (GstMssDemuxStream, 1);
131   stream->downloader = gst_uri_downloader_new ();
132
133   /* Streaming task */
134   g_static_rec_mutex_init (&stream->stream_lock);
135   stream->stream_task =
136       gst_task_create ((GstTaskFunction) gst_mss_demux_stream_loop, stream);
137   gst_task_set_lock (stream->stream_task, &stream->stream_lock);
138
139   stream->pad = srcpad;
140   stream->manifest_stream = manifeststream;
141   stream->parent = mssdemux;
142
143   return stream;
144 }
145
146 static void
147 gst_mss_demux_stream_free (GstMssDemuxStream * stream)
148 {
149   if (stream->stream_task) {
150     if (GST_TASK_STATE (stream->stream_task) != GST_TASK_STOPPED) {
151       GST_DEBUG_OBJECT (stream->parent, "Leaving streaming task %s:%s",
152           GST_DEBUG_PAD_NAME (stream->pad));
153       gst_task_stop (stream->stream_task);
154       g_static_rec_mutex_lock (&stream->stream_lock);
155       g_static_rec_mutex_unlock (&stream->stream_lock);
156       GST_LOG_OBJECT (stream->parent, "Waiting for task to finish");
157       gst_task_join (stream->stream_task);
158       GST_LOG_OBJECT (stream->parent, "Finished");
159     }
160     gst_object_unref (stream->stream_task);
161     g_static_rec_mutex_free (&stream->stream_lock);
162     stream->stream_task = NULL;
163   }
164
165   if (stream->downloader != NULL) {
166     g_object_unref (stream->downloader);
167     stream->downloader = NULL;
168   }
169   if (stream->pad) {
170     gst_object_unref (stream->pad);
171     stream->pad = NULL;
172   }
173   g_free (stream);
174 }
175
176 static void
177 gst_mss_demux_reset (GstMssDemux * mssdemux)
178 {
179   GSList *iter;
180   if (mssdemux->manifest_buffer) {
181     gst_buffer_unref (mssdemux->manifest_buffer);
182     mssdemux->manifest_buffer = NULL;
183   }
184
185   for (iter = mssdemux->streams; iter; iter = g_slist_next (iter)) {
186     GstMssDemuxStream *stream = iter->data;
187     gst_element_remove_pad (GST_ELEMENT_CAST (mssdemux), stream->pad);
188     gst_mss_demux_stream_free (stream);
189   }
190   g_slist_free (mssdemux->streams);
191   mssdemux->streams = NULL;
192
193   if (mssdemux->manifest) {
194     gst_mss_manifest_free (mssdemux->manifest);
195     mssdemux->manifest = NULL;
196   }
197
198   mssdemux->n_videos = mssdemux->n_audios = 0;
199   g_free (mssdemux->base_url);
200   mssdemux->base_url = NULL;
201 }
202
203 static void
204 gst_mss_demux_dispose (GObject * object)
205 {
206   /* GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (object); */
207
208   G_OBJECT_CLASS (parent_class)->dispose (object);
209 }
210
211 static GstStateChangeReturn
212 gst_mss_demux_change_state (GstElement * element, GstStateChange transition)
213 {
214   GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (element);
215   GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE;
216
217   switch (transition) {
218     case GST_STATE_CHANGE_PAUSED_TO_READY:
219       gst_mss_demux_reset (mssdemux);
220       break;
221     case GST_STATE_CHANGE_READY_TO_NULL:
222       break;
223     default:
224       break;
225   }
226
227   result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
228
229   switch (transition) {
230     case GST_STATE_CHANGE_PAUSED_TO_READY:{
231       break;
232     }
233     default:
234       break;
235   }
236
237   return result;
238 }
239
240 static GstFlowReturn
241 gst_mss_demux_chain (GstPad * pad, GstBuffer * buffer)
242 {
243   GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (GST_PAD_PARENT (pad));
244   if (mssdemux->manifest_buffer == NULL)
245     mssdemux->manifest_buffer = buffer;
246   else
247     mssdemux->manifest_buffer =
248         gst_buffer_join (mssdemux->manifest_buffer, buffer);
249
250   return GST_FLOW_OK;
251 }
252
253 static void
254 gst_mss_demux_start (GstMssDemux * mssdemux)
255 {
256   GSList *iter;
257
258   GST_INFO_OBJECT (mssdemux, "Starting streams' tasks");
259   for (iter = mssdemux->streams; iter; iter = g_slist_next (iter)) {
260     GstMssDemuxStream *stream = iter->data;
261     gst_task_start (stream->stream_task);
262   }
263 }
264
265 static gboolean
266 gst_mss_demux_event (GstPad * pad, GstEvent * event)
267 {
268   GstMssDemux *mssdemux = GST_MSS_DEMUX_CAST (GST_PAD_PARENT (pad));
269   gboolean forward = TRUE;
270   gboolean ret = TRUE;
271
272   switch (GST_EVENT_TYPE (event)) {
273     case GST_EVENT_EOS:
274       if (mssdemux->manifest_buffer == NULL) {
275         GST_WARNING_OBJECT (mssdemux, "Received EOS without a manifest.");
276         break;
277       }
278
279       gst_mss_demux_process_manifest (mssdemux);
280       gst_mss_demux_start (mssdemux);
281       forward = FALSE;
282       break;
283     default:
284       break;
285   }
286
287   if (forward) {
288     ret = gst_pad_event_default (pad, event);
289   } else {
290     gst_event_unref (event);
291   }
292
293   return ret;
294 }
295
296 static void
297 gst_mss_demux_create_streams (GstMssDemux * mssdemux)
298 {
299   GSList *streams = gst_mss_manifest_get_streams (mssdemux->manifest);
300   GSList *iter;
301
302   if (streams == NULL) {
303     GST_INFO_OBJECT (mssdemux, "No streams found in the manifest");
304     /* TODO  post eos? */
305   }
306
307   for (iter = streams; iter; iter = g_slist_next (iter)) {
308     gchar *name;
309     GstPad *srcpad = NULL;
310     GstMssDemuxStream *stream = NULL;
311     GstMssStream *manifeststream = iter->data;
312     GstMssStreamType streamtype;
313
314     streamtype = gst_mss_stream_get_type (manifeststream);
315     GST_DEBUG_OBJECT (mssdemux, "Found stream of type: %s",
316         gst_mss_stream_type_name (streamtype));
317
318     /* TODO use stream's name as the pad name? */
319     if (streamtype == MSS_STREAM_TYPE_VIDEO) {
320       name = g_strdup_printf ("video_%02u", mssdemux->n_videos++);
321       srcpad =
322           gst_pad_new_from_static_template (&gst_mss_demux_videosrc_template,
323           name);
324       g_free (name);
325     } else if (streamtype == MSS_STREAM_TYPE_AUDIO) {
326       name = g_strdup_printf ("audio_%02u", mssdemux->n_audios++);
327       srcpad =
328           gst_pad_new_from_static_template (&gst_mss_demux_audiosrc_template,
329           name);
330       g_free (name);
331     }
332
333     if (!srcpad) {
334       GST_WARNING_OBJECT (mssdemux, "Ignoring unknown type stream");
335       continue;
336     }
337
338     stream = gst_mss_demux_stream_new (mssdemux, manifeststream, srcpad);
339     mssdemux->streams = g_slist_append (mssdemux->streams, stream);
340   }
341 }
342
343 static void
344 gst_mss_demux_expose_stream (GstMssDemux * mssdemux, GstMssDemuxStream * stream)
345 {
346   GstCaps *caps;
347   GstPad *pad = stream->pad;
348
349   caps = gst_mss_stream_get_caps (stream->manifest_stream);
350
351   if (caps) {
352     gst_pad_set_caps (pad, caps);
353     gst_caps_unref (caps);
354
355     gst_pad_set_active (pad, TRUE);
356     GST_INFO_OBJECT (mssdemux, "Adding srcpad %s:%s with caps %" GST_PTR_FORMAT,
357         GST_DEBUG_PAD_NAME (pad), caps);
358     gst_object_ref (pad);
359     gst_element_add_pad (GST_ELEMENT_CAST (mssdemux), pad);
360   } else {
361     GST_WARNING_OBJECT (mssdemux, "Not exposing stream of unrecognized format");
362   }
363 }
364
365 static void
366 gst_mss_demux_process_manifest (GstMssDemux * mssdemux)
367 {
368   GstQuery *query;
369   gchar *uri = NULL;
370   gboolean ret;
371   GSList *iter;
372
373   g_return_if_fail (mssdemux->manifest_buffer != NULL);
374   g_return_if_fail (mssdemux->manifest == NULL);
375
376   query = gst_query_new_uri ();
377   ret = gst_pad_peer_query (mssdemux->sinkpad, query);
378   if (ret) {
379     gchar *baseurl_end;
380     gst_query_parse_uri (query, &uri);
381     GST_INFO_OBJECT (mssdemux, "Upstream is using URI: %s", uri);
382
383     baseurl_end = g_strrstr (uri, "/Manifest");
384     if (baseurl_end) {
385       /* set the new end of the string */
386       baseurl_end[0] = '\0';
387     } else {
388       GST_WARNING_OBJECT (mssdemux, "Stream's URI didn't end with /manifest");
389     }
390
391     mssdemux->base_url = uri;
392   }
393   gst_query_unref (query);
394
395   mssdemux->manifest = gst_mss_manifest_new (mssdemux->manifest_buffer);
396   if (!mssdemux->manifest) {
397     GST_ELEMENT_ERROR (mssdemux, STREAM, FORMAT, ("Bad manifest file"),
398         ("Xml manifest file couldn't be parsed"));
399     return;
400   }
401
402   gst_mss_demux_create_streams (mssdemux);
403   for (iter = mssdemux->streams; iter; iter = g_slist_next (iter)) {
404     gst_mss_demux_expose_stream (mssdemux, iter->data);
405   }
406 }
407
408 static void
409 gst_mss_demux_stream_loop (GstMssDemuxStream * stream)
410 {
411   GstMssDemux *mssdemux = stream->parent;
412   gchar *path;
413   gchar *url;
414   GstFragment *fragment;
415   GstBuffer *buffer;
416   GstFlowReturn ret;
417
418   GST_DEBUG_OBJECT (mssdemux, "Getting url for stream %p", stream);
419   ret = gst_mss_stream_get_fragment_url (stream->manifest_stream, &path);
420   switch (ret) {
421     case GST_FLOW_OK:
422       break;                    /* all is good, let's go */
423     case GST_FLOW_UNEXPECTED:  /* EOS */
424       goto eos;
425     case GST_FLOW_ERROR:
426       goto error;
427     default:
428       break;
429   }
430   if (!path) {
431     /* TODO */
432   }
433   GST_DEBUG_OBJECT (mssdemux, "Got url path '%s' for stream %p", path, stream);
434
435   url = g_strdup_printf ("%s/%s", mssdemux->base_url, path);
436
437   fragment = gst_uri_downloader_fetch_uri (stream->downloader, url);
438   g_free (path);
439   g_free (url);
440
441   buffer = gst_fragment_get_buffer (fragment);
442   buffer = gst_buffer_make_metadata_writable (buffer);
443   gst_buffer_set_caps (buffer, GST_PAD_CAPS (stream->pad));
444
445   ret = gst_pad_push (stream->pad, buffer);     /* TODO check return */
446   switch (ret) {
447     case GST_FLOW_UNEXPECTED:
448       goto eos;                 /* EOS ? */
449     case GST_FLOW_ERROR:
450       goto error;
451     case GST_FLOW_NOT_LINKED:
452       break;                    /* TODO what to do here? pause the task or just keep pushing? */
453     case GST_FLOW_OK:
454     default:
455       break;
456   }
457
458   gst_mss_stream_advance_fragment (stream->manifest_stream);
459   return;
460
461 eos:
462   {
463     GstEvent *eos = gst_event_new_eos ();
464     GST_DEBUG_OBJECT (mssdemux, "Pushing EOS on pad %s:%s",
465         GST_DEBUG_PAD_NAME (stream->pad));
466     gst_pad_push_event (stream->pad, eos);
467     gst_task_stop (stream->stream_task);
468     return;
469   }
470 error:
471   {
472     GST_WARNING_OBJECT (mssdemux, "Error while pushing fragment");
473     gst_task_stop (stream->stream_task);
474     return;
475   }
476 }