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