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>
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.
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.
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.
23 * SECTION:element-icydemux
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.
32 * <title>Example launch line</title>
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.)
45 #include <gst/gst-i18n-plugin.h>
46 #include <gst/tag/tag.h>
48 #include "gsticydemux.h"
52 #define ICY_TYPE_FIND_MAX_SIZE (40*1024)
54 GST_DEBUG_CATEGORY_STATIC (icydemux_debug);
55 #define GST_CAT_DEFAULT (icydemux_debug)
57 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
60 GST_STATIC_CAPS ("application/x-icy, metadata-interval = (int)[0, MAX]")
63 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
66 GST_STATIC_CAPS ("ANY")
69 static void gst_icydemux_dispose (GObject * object);
71 static GstFlowReturn gst_icydemux_chain (GstPad * pad, GstObject * parent,
73 static gboolean gst_icydemux_handle_event (GstPad * pad, GstObject * parent,
76 static gboolean gst_icydemux_add_srcpad (GstICYDemux * icydemux,
78 static gboolean gst_icydemux_remove_srcpad (GstICYDemux * icydemux);
80 static GstStateChangeReturn gst_icydemux_change_state (GstElement * element,
81 GstStateChange transition);
82 static gboolean gst_icydemux_sink_setcaps (GstPad * pad, GstCaps * caps);
84 static gboolean gst_icydemux_send_tag_event (GstICYDemux * icydemux,
85 GstTagList * taglist);
88 #define gst_icydemux_parent_class parent_class
89 G_DEFINE_TYPE (GstICYDemux, gst_icydemux, GST_TYPE_ELEMENT);
92 gst_icydemux_class_init (GstICYDemuxClass * klass)
94 GObjectClass *gobject_class;
95 GstElementClass *gstelement_class;
97 gobject_class = (GObjectClass *) klass;
98 gstelement_class = (GstElementClass *) klass;
100 parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
102 gobject_class->dispose = gst_icydemux_dispose;
104 gstelement_class->change_state = gst_icydemux_change_state;
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));
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>");
119 gst_icydemux_reset (GstICYDemux * icydemux)
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)
124 icydemux->meta_interval = -1;
125 icydemux->remaining = 0;
127 icydemux->typefinding = TRUE;
129 gst_caps_replace (&(icydemux->src_caps), NULL);
131 gst_icydemux_remove_srcpad (icydemux);
133 if (icydemux->cached_tags) {
134 gst_tag_list_unref (icydemux->cached_tags);
135 icydemux->cached_tags = NULL;
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;
145 if (icydemux->meta_adapter) {
146 gst_adapter_clear (icydemux->meta_adapter);
147 g_object_unref (icydemux->meta_adapter);
148 icydemux->meta_adapter = NULL;
151 if (icydemux->typefind_buf) {
152 gst_buffer_unref (icydemux->typefind_buf);
153 icydemux->typefind_buf = NULL;
156 if (icydemux->content_type) {
157 g_free (icydemux->content_type);
158 icydemux->content_type = NULL;
163 gst_icydemux_init (GstICYDemux * icydemux)
165 GstElementClass *klass = GST_ELEMENT_GET_CLASS (icydemux);
168 gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
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);
176 gst_icydemux_reset (icydemux);
180 gst_icydemux_sink_setcaps (GstPad * pad, GstCaps * caps)
182 GstICYDemux *icydemux = GST_ICYDEMUX (GST_PAD_PARENT (pad));
183 GstStructure *structure = gst_caps_get_structure (caps, 0);
186 if (!gst_structure_get_int (structure, "metadata-interval",
187 &icydemux->meta_interval))
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);
194 /* We have a meta interval, so initialise the rest */
195 icydemux->remaining = icydemux->meta_interval;
196 icydemux->meta_remaining = 0;
201 gst_icydemux_dispose (GObject * object)
203 GstICYDemux *icydemux = GST_ICYDEMUX (object);
205 gst_icydemux_reset (icydemux);
207 G_OBJECT_CLASS (parent_class)->dispose (object);
214 } CopyStickyEventsData;
217 copy_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data)
219 CopyStickyEventsData *data = user_data;
221 if (GST_EVENT_TYPE (*event) >= GST_EVENT_CAPS && data->caps) {
222 gst_pad_set_caps (data->pad, data->caps);
226 if (GST_EVENT_TYPE (*event) != GST_EVENT_CAPS)
227 gst_pad_push_event (data->pad, gst_event_ref (*event));
233 gst_icydemux_add_srcpad (GstICYDemux * icydemux, GstCaps * new_caps)
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,
242 gst_pad_set_caps (icydemux->srcpad, icydemux->src_caps);
245 /* Caps never changed */
246 gst_caps_unref (new_caps);
249 if (icydemux->srcpad == NULL) {
250 CopyStickyEventsData data;
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);
257 gst_pad_use_fixed_caps (icydemux->srcpad);
258 gst_pad_set_active (icydemux->srcpad, TRUE);
260 data.pad = icydemux->srcpad;
261 data.caps = icydemux->src_caps;
262 gst_pad_sticky_events_foreach (icydemux->sinkpad, copy_sticky_events,
265 gst_pad_set_caps (data.pad, data.caps);
267 GST_DEBUG_OBJECT (icydemux, "Adding src pad with caps %" GST_PTR_FORMAT,
270 if (!(gst_element_add_pad (GST_ELEMENT (icydemux), icydemux->srcpad)))
272 gst_element_no_more_pads (GST_ELEMENT (icydemux));
279 gst_icydemux_remove_srcpad (GstICYDemux * icydemux)
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;
293 gst_icydemux_unicodify (const gchar * str)
295 const gchar *env_vars[] = { "GST_ICY_TAG_ENCODING",
296 "GST_TAG_ENCODING", NULL
299 return gst_tag_freeform_string_to_utf8 (str, -1, env_vars);
302 /* takes ownership of tag list */
304 gst_icydemux_tag_found (GstICYDemux * icydemux, GstTagList * tags)
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);
310 /* if we haven't a source pad yet, cache the tags */
311 if (!icydemux->cached_tags) {
312 icydemux->cached_tags = tags;
314 gst_tag_list_insert (icydemux->cached_tags, tags,
315 GST_TAG_MERGE_REPLACE_ALL);
316 gst_tag_list_unref (tags);
323 gst_icydemux_parse_and_send_tags (GstICYDemux * icydemux)
331 length = gst_adapter_available (icydemux->meta_adapter);
333 data = gst_adapter_map (icydemux->meta_adapter, length);
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);
339 tags = gst_tag_list_new_empty ();
340 strings = g_strsplit (buffer, "';", 0);
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);
346 if (title && *title) {
347 gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_TITLE,
351 } else if (!g_ascii_strncasecmp (strings[i], "StreamUrl=", 10)) {
352 char *url = gst_icydemux_unicodify (strings[i] + 11);
355 gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_HOMEPAGE,
362 g_strfreev (strings);
364 gst_adapter_unmap (icydemux->meta_adapter);
365 gst_adapter_flush (icydemux->meta_adapter, length);
367 if (!gst_tag_list_is_empty (tags))
368 gst_icydemux_tag_found (icydemux, tags);
370 gst_tag_list_unref (tags);
374 gst_icydemux_handle_event (GstPad * pad, GstObject * parent, GstEvent * event)
376 GstICYDemux *icydemux = GST_ICYDEMUX (parent);
379 switch (GST_EVENT_TYPE (event)) {
384 gst_event_parse_tag (event, &tags);
385 result = gst_icydemux_tag_found (icydemux, gst_tag_list_copy (tags));
386 gst_event_unref (event);
393 gst_event_parse_caps (event, &caps);
394 result = gst_icydemux_sink_setcaps (pad, caps);
395 gst_event_unref (event);
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;
410 return gst_pad_event_default (pad, parent, event);
412 if (!GST_EVENT_IS_STICKY (event))
413 icydemux->cached_events =
414 g_list_append (icydemux->cached_events, event);
418 return gst_pad_event_default (pad, parent, event);
423 gst_icydemux_send_cached_events (GstICYDemux * icydemux)
427 for (l = icydemux->cached_events; l != NULL; l = l->next) {
428 GstEvent *event = GST_EVENT (l->data);
430 gst_pad_push_event (icydemux->srcpad, event);
432 g_list_free (icydemux->cached_events);
433 icydemux->cached_events = NULL;
437 gst_icydemux_typefind_or_forward (GstICYDemux * icydemux, GstBuffer * buf)
439 if (icydemux->typefinding) {
441 GstCaps *caps = NULL;
442 GstTypeFindProbability prob;
444 /* If we have a content-type from upstream, let's see if we can shortcut
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");
451 GST_DEBUG ("Upstream Content-Type isn't supported");
452 g_free (icydemux->content_type);
453 icydemux->content_type = NULL;
457 if (icydemux->typefind_buf) {
458 icydemux->typefind_buf = gst_buffer_append (icydemux->typefind_buf, buf);
460 icydemux->typefind_buf = buf;
463 /* Only typefind if we haven't already got some caps */
465 caps = gst_type_find_helper_for_buffer (GST_OBJECT (icydemux),
466 icydemux->typefind_buf, &prob);
469 if (gst_buffer_get_size (icydemux->typefind_buf) <
470 ICY_TYPE_FIND_MAX_SIZE) {
471 /* Just break for more data */
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;
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;
491 gst_caps_unref (caps);
493 if (icydemux->cached_events) {
494 gst_icydemux_send_cached_events (icydemux);
497 if (icydemux->cached_tags) {
498 gst_icydemux_send_tag_event (icydemux, icydemux->cached_tags);
499 icydemux->cached_tags = NULL;
502 /* Move onto streaming: call ourselves recursively with the typefind buffer
503 * to get that forwarded. */
504 icydemux->typefinding = FALSE;
506 tf_buf = icydemux->typefind_buf;
507 icydemux->typefind_buf = NULL;
508 return gst_icydemux_typefind_or_forward (icydemux, tf_buf);
510 if (G_UNLIKELY (icydemux->srcpad == NULL)) {
511 gst_buffer_unref (buf);
512 return GST_FLOW_ERROR;
515 buf = gst_buffer_make_writable (buf);
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;
523 return gst_pad_push (icydemux->srcpad, buf);
528 gst_icydemux_add_meta (GstICYDemux * icydemux, GstBuffer * buf)
530 if (!icydemux->meta_adapter)
531 icydemux->meta_adapter = gst_adapter_new ();
533 gst_adapter_push (icydemux->meta_adapter, buf);
537 gst_icydemux_chain (GstPad * pad, GstObject * parent, GstBuffer * buf)
539 GstICYDemux *icydemux;
540 guint size, chunk, offset;
542 GstFlowReturn ret = GST_FLOW_OK;
544 icydemux = GST_ICYDEMUX (parent);
546 if (G_UNLIKELY (icydemux->meta_interval < 0))
549 if (icydemux->meta_interval == 0) {
550 ret = gst_icydemux_typefind_or_forward (icydemux, buf);
554 /* Go through the buffer, chopping it into appropriate chunks. Forward as
555 * tags or buffers, as appropriate
557 size = gst_buffer_get_size (buf);
560 if (icydemux->remaining) {
561 chunk = (size <= icydemux->remaining) ? size : icydemux->remaining;
562 if (offset == 0 && chunk == size) {
566 sub = gst_buffer_copy_region (buf, GST_BUFFER_COPY_ALL, offset, chunk);
569 icydemux->remaining -= chunk;
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)
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);
583 icydemux->meta_remaining -= chunk;
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);
591 icydemux->remaining = icydemux->meta_interval;
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).
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;
612 gst_buffer_unref (buf);
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;
626 static GstStateChangeReturn
627 gst_icydemux_change_state (GstElement * element, GstStateChange transition)
629 GstStateChangeReturn ret;
630 GstICYDemux *icydemux = GST_ICYDEMUX (element);
632 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
634 switch (transition) {
635 case GST_STATE_CHANGE_PAUSED_TO_READY:
636 gst_icydemux_reset (icydemux);
644 /* takes ownership of tag list */
646 gst_icydemux_send_tag_event (GstICYDemux * icydemux, GstTagList * tags)
650 event = gst_event_new_tag (tags);
651 GST_EVENT_TIMESTAMP (event) = 0;
653 GST_DEBUG_OBJECT (icydemux, "Sending tag event on src pad");
654 return gst_pad_push_event (icydemux->srcpad, event);
659 plugin_init (GstPlugin * plugin)
661 GST_DEBUG_CATEGORY_INIT (icydemux_debug, "icydemux", 0,
662 "GStreamer ICY tag demuxer");
664 return gst_element_register (plugin, "icydemux",
665 GST_RANK_PRIMARY, GST_TYPE_ICYDEMUX);
668 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
671 "Demux ICY tags from a stream",
672 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)