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