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