Merge branch 'master' into 0.11
[platform/upstream/gstreamer.git] / gst / icydemux / gsticydemux.c
1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */
2 /* Copyright 2005 Jan Schmidt <thaytan@mad.scientist.com>
3  *           2006 Michael Smith <msmith@fluendo.com>
4  * Copyright (C) 2003-2004 Benjamin Otte <otte@gnome.org>
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 /**
23  * SECTION:element-icydemux
24  *
25  * icydemux accepts data streams with ICY metadata at known intervals, as
26  * transmitted from an upstream element (usually read as response headers from
27  * an HTTP stream). The mime type of the data between the tag blocks is
28  * detected using typefind functions, and the appropriate output mime type set
29  * on outgoing buffers. 
30  *
31  * <refsect2>
32  * <title>Example launch line</title>
33  * |[
34  * gst-launch souphttpsrc location=http://some.server/ iradio-mode=true ! icydemux ! fakesink -t
35  * ]| This pipeline should read any available ICY tag information and output it.
36  * The contents of the stream should be detected, and the appropriate mime
37  * type set on buffers produced from icydemux. (Using gnomevfssrc, neonhttpsrc
38  * or giosrc instead of souphttpsrc should also work.)
39  * </refsect2>
40  */
41 #ifdef HAVE_CONFIG_H
42 #include "config.h"
43 #endif
44 #include <gst/gst.h>
45 #include <gst/gst-i18n-plugin.h>
46 #include <gst/tag/tag.h>
47
48 #include "gsticydemux.h"
49
50 #include <string.h>
51
52 #define ICY_TYPE_FIND_MAX_SIZE (40*1024)
53
54 GST_DEBUG_CATEGORY_STATIC (icydemux_debug);
55 #define GST_CAT_DEFAULT (icydemux_debug)
56
57 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
58     GST_PAD_SINK,
59     GST_PAD_ALWAYS,
60     GST_STATIC_CAPS ("application/x-icy, metadata-interval = (int)[0, MAX]")
61     );
62
63 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
64     GST_PAD_SRC,
65     GST_PAD_SOMETIMES,
66     GST_STATIC_CAPS ("ANY")
67     );
68
69 static void gst_icydemux_dispose (GObject * object);
70
71 static GstFlowReturn gst_icydemux_chain (GstPad * pad, GstBuffer * buf);
72 static gboolean gst_icydemux_handle_event (GstPad * pad, GstEvent * event);
73
74 static gboolean gst_icydemux_add_srcpad (GstICYDemux * icydemux,
75     GstCaps * new_caps);
76 static gboolean gst_icydemux_remove_srcpad (GstICYDemux * icydemux);
77
78 static GstStateChangeReturn gst_icydemux_change_state (GstElement * element,
79     GstStateChange transition);
80 static gboolean gst_icydemux_sink_setcaps (GstPad * pad, GstCaps * caps);
81
82 static gboolean gst_icydemux_send_tag_event (GstICYDemux * icydemux,
83     GstTagList * taglist);
84
85
86 #define gst_icydemux_parent_class parent_class
87 G_DEFINE_TYPE (GstICYDemux, gst_icydemux, GST_TYPE_ELEMENT);
88
89 static void
90 gst_icydemux_class_init (GstICYDemuxClass * klass)
91 {
92   GObjectClass *gobject_class;
93   GstElementClass *gstelement_class;
94
95   gobject_class = (GObjectClass *) klass;
96   gstelement_class = (GstElementClass *) klass;
97
98   parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
99
100   gobject_class->dispose = gst_icydemux_dispose;
101
102   gstelement_class->change_state = gst_icydemux_change_state;
103
104   gst_element_class_add_pad_template (gstelement_class,
105       gst_static_pad_template_get (&src_factory));
106   gst_element_class_add_pad_template (gstelement_class,
107       gst_static_pad_template_get (&sink_factory));
108
109   gst_element_class_set_details_simple (gstelement_class, "ICY tag demuxer",
110       "Codec/Demuxer/Metadata",
111       "Read and output ICY tags while demuxing the contents",
112       "Jan Schmidt <thaytan@mad.scientist.com>, "
113       "Michael Smith <msmith@fluendo.com>");
114 }
115
116 static void
117 gst_icydemux_reset (GstICYDemux * icydemux)
118 {
119   /* Unknown at the moment (this is a fatal error if don't have a value by the
120    * time we get to our chain function)
121    */
122   icydemux->meta_interval = -1;
123   icydemux->remaining = 0;
124
125   icydemux->typefinding = TRUE;
126
127   gst_caps_replace (&(icydemux->src_caps), NULL);
128
129   gst_icydemux_remove_srcpad (icydemux);
130
131   if (icydemux->cached_tags) {
132     gst_tag_list_free (icydemux->cached_tags);
133     icydemux->cached_tags = NULL;
134   }
135
136   if (icydemux->cached_events) {
137     g_list_foreach (icydemux->cached_events,
138         (GFunc) gst_mini_object_unref, NULL);
139     g_list_free (icydemux->cached_events);
140     icydemux->cached_events = NULL;
141   }
142
143   if (icydemux->meta_adapter) {
144     gst_adapter_clear (icydemux->meta_adapter);
145     g_object_unref (icydemux->meta_adapter);
146     icydemux->meta_adapter = NULL;
147   }
148
149   if (icydemux->typefind_buf) {
150     gst_buffer_unref (icydemux->typefind_buf);
151     icydemux->typefind_buf = NULL;
152   }
153
154   if (icydemux->content_type) {
155     g_free (icydemux->content_type);
156     icydemux->content_type = NULL;
157   }
158 }
159
160 static void
161 gst_icydemux_init (GstICYDemux * icydemux)
162 {
163   GstElementClass *klass = GST_ELEMENT_GET_CLASS (icydemux);
164
165   icydemux->sinkpad =
166       gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
167           "sink"), "sink");
168   gst_pad_set_chain_function (icydemux->sinkpad,
169       GST_DEBUG_FUNCPTR (gst_icydemux_chain));
170   gst_pad_set_event_function (icydemux->sinkpad,
171       GST_DEBUG_FUNCPTR (gst_icydemux_handle_event));
172   gst_element_add_pad (GST_ELEMENT (icydemux), icydemux->sinkpad);
173
174   gst_icydemux_reset (icydemux);
175 }
176
177 static gboolean
178 gst_icydemux_sink_setcaps (GstPad * pad, GstCaps * caps)
179 {
180   GstICYDemux *icydemux = GST_ICYDEMUX (GST_PAD_PARENT (pad));
181   GstStructure *structure = gst_caps_get_structure (caps, 0);
182   const gchar *tmp;
183
184   if (!gst_structure_get_int (structure, "metadata-interval",
185           &icydemux->meta_interval))
186     return FALSE;
187
188   /* If incoming caps have the HTTP Content-Type, copy that over */
189   if ((tmp = gst_structure_get_string (structure, "content-type")))
190     icydemux->content_type = g_strdup (tmp);
191
192   /* We have a meta interval, so initialise the rest */
193   icydemux->remaining = icydemux->meta_interval;
194   icydemux->meta_remaining = 0;
195   return TRUE;
196 }
197
198 static void
199 gst_icydemux_dispose (GObject * object)
200 {
201   GstICYDemux *icydemux = GST_ICYDEMUX (object);
202
203   gst_icydemux_reset (icydemux);
204
205   G_OBJECT_CLASS (parent_class)->dispose (object);
206 }
207
208 static gboolean
209 gst_icydemux_add_srcpad (GstICYDemux * icydemux, GstCaps * new_caps)
210 {
211   if (icydemux->src_caps == NULL ||
212       !gst_caps_is_equal (new_caps, icydemux->src_caps)) {
213     gst_caps_replace (&(icydemux->src_caps), new_caps);
214     if (icydemux->srcpad != NULL) {
215       GST_DEBUG_OBJECT (icydemux, "Changing src pad caps to %" GST_PTR_FORMAT,
216           icydemux->src_caps);
217
218       gst_pad_set_caps (icydemux->srcpad, icydemux->src_caps);
219     }
220   } else {
221     /* Caps never changed */
222     gst_caps_unref (new_caps);
223   }
224
225   if (icydemux->srcpad == NULL) {
226     icydemux->srcpad =
227         gst_pad_new_from_template (gst_element_class_get_pad_template
228         (GST_ELEMENT_GET_CLASS (icydemux), "src"), "src");
229     g_return_val_if_fail (icydemux->srcpad != NULL, FALSE);
230
231     gst_pad_use_fixed_caps (icydemux->srcpad);
232
233     if (icydemux->src_caps)
234       gst_pad_set_caps (icydemux->srcpad, icydemux->src_caps);
235
236     GST_DEBUG_OBJECT (icydemux, "Adding src pad with caps %" GST_PTR_FORMAT,
237         icydemux->src_caps);
238
239     gst_pad_set_active (icydemux->srcpad, TRUE);
240     if (!(gst_element_add_pad (GST_ELEMENT (icydemux), icydemux->srcpad)))
241       return FALSE;
242     gst_element_no_more_pads (GST_ELEMENT (icydemux));
243   }
244
245   return TRUE;
246 }
247
248 static gboolean
249 gst_icydemux_remove_srcpad (GstICYDemux * icydemux)
250 {
251   gboolean res = TRUE;
252
253   if (icydemux->srcpad != NULL) {
254     res = gst_element_remove_pad (GST_ELEMENT (icydemux), icydemux->srcpad);
255     g_return_val_if_fail (res != FALSE, FALSE);
256     icydemux->srcpad = NULL;
257   }
258
259   return res;
260 };
261
262 static gchar *
263 gst_icydemux_unicodify (const gchar * str)
264 {
265   const gchar *env_vars[] = { "GST_ICY_TAG_ENCODING",
266     "GST_TAG_ENCODING", NULL
267   };
268
269   return gst_tag_freeform_string_to_utf8 (str, -1, env_vars);
270 }
271
272 /* takes ownership of tag list */
273 static gboolean
274 gst_icydemux_tag_found (GstICYDemux * icydemux, GstTagList * tags)
275 {
276   /* send the tag event if we have finished typefinding and have a src pad */
277   if (icydemux->srcpad)
278     return gst_icydemux_send_tag_event (icydemux, tags);
279
280   /* if we haven't a source pad yet, cache the tags */
281   if (!icydemux->cached_tags) {
282     icydemux->cached_tags = tags;
283   } else {
284     gst_tag_list_insert (icydemux->cached_tags, tags,
285         GST_TAG_MERGE_REPLACE_ALL);
286     gst_tag_list_free (tags);
287   }
288
289   return TRUE;
290 }
291
292 static void
293 gst_icydemux_parse_and_send_tags (GstICYDemux * icydemux)
294 {
295   GstTagList *tags;
296   const guint8 *data;
297   int length, i;
298   gchar *buffer;
299   gchar **strings;
300
301   length = gst_adapter_available (icydemux->meta_adapter);
302
303   data = gst_adapter_map (icydemux->meta_adapter, length);
304
305   /* Now, copy this to a buffer where we can NULL-terminate it to make things
306    * a bit easier, then do that parsing. */
307   buffer = g_strndup ((const gchar *) data, length);
308
309   tags = gst_tag_list_new_empty ();
310   strings = g_strsplit (buffer, "';", 0);
311
312   for (i = 0; strings[i]; i++) {
313     if (!g_ascii_strncasecmp (strings[i], "StreamTitle=", 12)) {
314       char *title = gst_icydemux_unicodify (strings[i] + 13);
315
316       if (title && *title) {
317         gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_TITLE,
318             title, NULL);
319         g_free (title);
320       }
321     } else if (!g_ascii_strncasecmp (strings[i], "StreamUrl=", 10)) {
322       char *url = gst_icydemux_unicodify (strings[i] + 11);
323
324       if (url && *url) {
325         gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_HOMEPAGE,
326             url, NULL);
327         g_free (url);
328       }
329     }
330   }
331
332   g_strfreev (strings);
333   g_free (buffer);
334   gst_adapter_unmap (icydemux->meta_adapter);
335   gst_adapter_flush (icydemux->meta_adapter, length);
336
337   if (!gst_tag_list_is_empty (tags))
338     gst_icydemux_tag_found (icydemux, tags);
339   else
340     gst_tag_list_free (tags);
341 }
342
343 static gboolean
344 gst_icydemux_handle_event (GstPad * pad, GstEvent * event)
345 {
346   GstICYDemux *icydemux = GST_ICYDEMUX (GST_PAD_PARENT (pad));
347   gboolean result;
348
349   switch (GST_EVENT_TYPE (event)) {
350     case GST_EVENT_TAG:
351     {
352       GstTagList *tags;
353
354       gst_event_parse_tag (event, &tags);
355       result = gst_icydemux_tag_found (icydemux, gst_tag_list_copy (tags));
356       gst_event_unref (event);
357       return result;
358     }
359     case GST_EVENT_CAPS:
360     {
361       GstCaps *caps;
362
363       gst_event_parse_caps (event, &caps);
364       result = gst_icydemux_sink_setcaps (pad, caps);
365       gst_event_unref (event);
366       return result;
367     }
368     default:
369       break;
370   }
371
372   if (icydemux->typefinding) {
373     switch (GST_EVENT_TYPE (event)) {
374       case GST_EVENT_FLUSH_STOP:
375         g_list_foreach (icydemux->cached_events,
376             (GFunc) gst_mini_object_unref, NULL);
377         g_list_free (icydemux->cached_events);
378         icydemux->cached_events = NULL;
379
380         return gst_pad_event_default (pad, event);
381       default:
382         icydemux->cached_events = g_list_append (icydemux->cached_events,
383             event);
384         return TRUE;
385     }
386   } else {
387     return gst_pad_event_default (pad, event);
388   }
389 }
390
391 static void
392 gst_icydemux_send_cached_events (GstICYDemux * icydemux)
393 {
394   GList *l;
395
396   for (l = icydemux->cached_events; l != NULL; l = l->next) {
397     GstEvent *event = GST_EVENT (l->data);
398
399     gst_pad_push_event (icydemux->srcpad, event);
400   }
401   g_list_free (icydemux->cached_events);
402   icydemux->cached_events = NULL;
403 }
404
405 static GstFlowReturn
406 gst_icydemux_typefind_or_forward (GstICYDemux * icydemux, GstBuffer * buf)
407 {
408   if (icydemux->typefinding) {
409     GstBuffer *tf_buf;
410     GstCaps *caps = NULL;
411     GstTypeFindProbability prob;
412
413     /* If we have a content-type from upstream, let's see if we can shortcut
414      * typefinding */
415     if (G_UNLIKELY (icydemux->content_type)) {
416       if (!g_ascii_strcasecmp (icydemux->content_type, "video/nsv")) {
417         GST_DEBUG ("We have a NSV stream");
418         caps = gst_caps_new_empty_simple ("video/x-nsv");
419       } else {
420         GST_DEBUG ("Upstream Content-Type isn't supported");
421         g_free (icydemux->content_type);
422         icydemux->content_type = NULL;
423       }
424     }
425
426     if (icydemux->typefind_buf) {
427       icydemux->typefind_buf = gst_buffer_join (icydemux->typefind_buf, buf);
428     } else {
429       icydemux->typefind_buf = buf;
430     }
431
432     /* Only typefind if we haven't already got some caps */
433     if (caps == NULL) {
434       caps = gst_type_find_helper_for_buffer (GST_OBJECT (icydemux),
435           icydemux->typefind_buf, &prob);
436
437       if (caps == NULL) {
438         if (gst_buffer_get_size (icydemux->typefind_buf) <
439             ICY_TYPE_FIND_MAX_SIZE) {
440           /* Just break for more data */
441           return GST_FLOW_OK;
442         }
443
444         /* We failed typefind */
445         GST_ELEMENT_ERROR (icydemux, STREAM, TYPE_NOT_FOUND, (NULL),
446             ("No caps found for contents within an ICY stream"));
447         gst_buffer_unref (icydemux->typefind_buf);
448         icydemux->typefind_buf = NULL;
449         return GST_FLOW_ERROR;
450       }
451     }
452
453     if (!gst_icydemux_add_srcpad (icydemux, caps)) {
454       GST_DEBUG_OBJECT (icydemux, "Failed to add srcpad");
455       gst_caps_unref (caps);
456       gst_buffer_unref (icydemux->typefind_buf);
457       icydemux->typefind_buf = NULL;
458       return GST_FLOW_ERROR;
459     }
460     gst_caps_unref (caps);
461
462     if (icydemux->cached_events) {
463       gst_icydemux_send_cached_events (icydemux);
464     }
465
466     if (icydemux->cached_tags) {
467       gst_icydemux_send_tag_event (icydemux, icydemux->cached_tags);
468       icydemux->cached_tags = NULL;
469     }
470
471     /* Move onto streaming: call ourselves recursively with the typefind buffer
472      * to get that forwarded. */
473     icydemux->typefinding = FALSE;
474
475     tf_buf = icydemux->typefind_buf;
476     icydemux->typefind_buf = NULL;
477     return gst_icydemux_typefind_or_forward (icydemux, tf_buf);
478   } else {
479     if (G_UNLIKELY (icydemux->srcpad == NULL)) {
480       gst_buffer_unref (buf);
481       return GST_FLOW_ERROR;
482     }
483
484     buf = gst_buffer_make_writable (buf);
485
486     /* Most things don't care, and it's a pain to track (we should preserve a
487      * 0 offset on the first buffer though if it's there, for id3demux etc.) */
488     if (GST_BUFFER_OFFSET (buf) != 0) {
489       GST_BUFFER_OFFSET (buf) = GST_BUFFER_OFFSET_NONE;
490     }
491
492     return gst_pad_push (icydemux->srcpad, buf);
493   }
494 }
495
496 static void
497 gst_icydemux_add_meta (GstICYDemux * icydemux, GstBuffer * buf)
498 {
499   if (!icydemux->meta_adapter)
500     icydemux->meta_adapter = gst_adapter_new ();
501
502   gst_adapter_push (icydemux->meta_adapter, buf);
503 }
504
505 static GstFlowReturn
506 gst_icydemux_chain (GstPad * pad, GstBuffer * buf)
507 {
508   GstICYDemux *icydemux;
509   guint size, chunk, offset;
510   GstBuffer *sub;
511   GstFlowReturn ret = GST_FLOW_OK;
512
513   icydemux = GST_ICYDEMUX (GST_PAD_PARENT (pad));
514
515   if (G_UNLIKELY (icydemux->meta_interval < 0))
516     goto not_negotiated;
517
518   if (icydemux->meta_interval == 0) {
519     ret = gst_icydemux_typefind_or_forward (icydemux, buf);
520     goto done;
521   }
522
523   /* Go through the buffer, chopping it into appropriate chunks. Forward as
524    * tags or buffers, as appropriate
525    */
526   size = gst_buffer_get_size (buf);
527   offset = 0;
528   while (size) {
529     if (icydemux->remaining) {
530       chunk = (size <= icydemux->remaining) ? size : icydemux->remaining;
531       sub = gst_buffer_copy_region (buf, GST_BUFFER_COPY_ALL, offset, chunk);
532       offset += chunk;
533       icydemux->remaining -= chunk;
534       size -= chunk;
535
536       /* This buffer goes onto typefinding, and/or directly pushed out */
537       ret = gst_icydemux_typefind_or_forward (icydemux, sub);
538       if (ret != GST_FLOW_OK)
539         goto done;
540     } else if (icydemux->meta_remaining) {
541       chunk = (size <= icydemux->meta_remaining) ?
542           size : icydemux->meta_remaining;
543       sub = gst_buffer_copy_region (buf, GST_BUFFER_COPY_ALL, offset, chunk);
544       gst_icydemux_add_meta (icydemux, sub);
545
546       offset += chunk;
547       icydemux->meta_remaining -= chunk;
548       size -= chunk;
549
550       if (icydemux->meta_remaining == 0) {
551         /* Parse tags from meta_adapter, send off as tag messages */
552         GST_DEBUG_OBJECT (icydemux, "No remaining metadata, parsing for tags");
553         gst_icydemux_parse_and_send_tags (icydemux);
554
555         icydemux->remaining = icydemux->meta_interval;
556       }
557     } else {
558       guint8 byte;
559       /* We need to read a single byte (always safe at this point in the loop)
560        * to figure out how many bytes of metadata exist. 
561        * The 'spec' tells us to read 16 * (byte_value) bytes of metadata after
562        * this (zero is common, and means the metadata hasn't changed).
563        */
564       gst_buffer_extract (buf, offset, &byte, 1);
565       icydemux->meta_remaining = 16 * byte;
566       if (icydemux->meta_remaining == 0)
567         icydemux->remaining = icydemux->meta_interval;
568
569       offset += 1;
570       size -= 1;
571     }
572   }
573
574 done:
575   gst_buffer_unref (buf);
576
577   return ret;
578
579   /* ERRORS */
580 not_negotiated:
581   {
582     GST_WARNING_OBJECT (icydemux, "meta_interval not set, buffer probably had "
583         "no caps set. Try enabling iradio-mode on the http source element");
584     gst_buffer_unref (buf);
585     return GST_FLOW_NOT_NEGOTIATED;
586   }
587 }
588
589 static GstStateChangeReturn
590 gst_icydemux_change_state (GstElement * element, GstStateChange transition)
591 {
592   GstStateChangeReturn ret;
593   GstICYDemux *icydemux = GST_ICYDEMUX (element);
594
595   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
596
597   switch (transition) {
598     case GST_STATE_CHANGE_PAUSED_TO_READY:
599       gst_icydemux_reset (icydemux);
600       break;
601     default:
602       break;
603   }
604   return ret;
605 }
606
607 /* takes ownership of tag list */
608 static gboolean
609 gst_icydemux_send_tag_event (GstICYDemux * icydemux, GstTagList * tags)
610 {
611   GstEvent *event;
612
613   gst_element_post_message (GST_ELEMENT (icydemux),
614       gst_message_new_tag (GST_OBJECT (icydemux), gst_tag_list_copy (tags)));
615
616   event = gst_event_new_tag (tags);
617   GST_EVENT_TIMESTAMP (event) = 0;
618
619   GST_DEBUG_OBJECT (icydemux, "Sending tag event on src pad");
620   return gst_pad_push_event (icydemux->srcpad, event);
621
622 }
623
624 static gboolean
625 plugin_init (GstPlugin * plugin)
626 {
627   GST_DEBUG_CATEGORY_INIT (icydemux_debug, "icydemux", 0,
628       "GStreamer ICY tag demuxer");
629
630   return gst_element_register (plugin, "icydemux",
631       GST_RANK_PRIMARY, GST_TYPE_ICYDEMUX);
632 }
633
634 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
635     GST_VERSION_MINOR,
636     "icydemux",
637     "Demux ICY tags from a stream",
638     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)