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., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, 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 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 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>");
59 #define ICY_TYPE_FIND_MAX_SIZE (40*1024)
61 GST_DEBUG_CATEGORY_STATIC (icydemux_debug);
62 #define GST_CAT_DEFAULT (icydemux_debug)
64 static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
67 GST_STATIC_CAPS ("application/x-icy, metadata-interval = (int)[0, MAX]")
70 static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
73 GST_STATIC_CAPS ("ANY")
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);
81 static GstFlowReturn gst_icydemux_chain (GstPad * pad, GstBuffer * buf);
82 static gboolean gst_icydemux_handle_event (GstPad * pad, GstEvent * event);
84 static gboolean gst_icydemux_add_srcpad (GstICYDemux * icydemux,
86 static gboolean gst_icydemux_remove_srcpad (GstICYDemux * icydemux);
88 static GstStateChangeReturn gst_icydemux_change_state (GstElement * element,
89 GstStateChange transition);
90 static gboolean gst_icydemux_sink_setcaps (GstPad * pad, GstCaps * caps);
92 static void gst_icydemux_send_tag_event (GstICYDemux * icydemux,
93 GstTagList * taglist);
95 static GstElementClass *parent_class = NULL;
98 gst_icydemux_get_type (void)
100 static GType plugin_type = 0;
103 static const GTypeInfo plugin_info = {
104 sizeof (GstICYDemuxClass),
105 (GBaseInitFunc) gst_icydemux_base_init,
107 (GClassInitFunc) gst_icydemux_class_init,
110 sizeof (GstICYDemux),
112 (GInstanceInitFunc) gst_icydemux_init,
114 plugin_type = g_type_register_static (GST_TYPE_ELEMENT,
115 "GstICYDemux", &plugin_info, 0);
121 gst_icydemux_base_init (GstICYDemuxClass * klass)
123 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
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);
133 gst_icydemux_class_init (GstICYDemuxClass * klass)
135 GObjectClass *gobject_class;
136 GstElementClass *gstelement_class;
138 gobject_class = (GObjectClass *) klass;
139 gstelement_class = (GstElementClass *) klass;
141 parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
143 gobject_class->dispose = gst_icydemux_dispose;
145 gstelement_class->change_state = gst_icydemux_change_state;
150 gst_icydemux_reset (GstICYDemux * icydemux)
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)
155 icydemux->meta_interval = -1;
156 icydemux->remaining = 0;
158 icydemux->typefinding = TRUE;
160 gst_caps_replace (&(icydemux->src_caps), NULL);
162 gst_icydemux_remove_srcpad (icydemux);
164 if (icydemux->cached_tags) {
165 gst_tag_list_free (icydemux->cached_tags);
166 icydemux->cached_tags = NULL;
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;
176 if (icydemux->meta_adapter) {
177 gst_adapter_clear (icydemux->meta_adapter);
178 g_object_unref (icydemux->meta_adapter);
179 icydemux->meta_adapter = NULL;
182 if (icydemux->typefind_buf) {
183 gst_buffer_unref (icydemux->typefind_buf);
184 icydemux->typefind_buf = NULL;
189 gst_icydemux_init (GstICYDemux * icydemux)
191 GstElementClass *klass = GST_ELEMENT_GET_CLASS (icydemux);
194 gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
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);
204 gst_icydemux_reset (icydemux);
208 gst_icydemux_sink_setcaps (GstPad * pad, GstCaps * caps)
210 GstICYDemux *icydemux = GST_ICYDEMUX (GST_PAD_PARENT (pad));
211 GstStructure *structure = gst_caps_get_structure (caps, 0);
213 if (!gst_structure_get_int (structure, "metadata-interval",
214 &icydemux->meta_interval))
217 /* We have a meta interval, so initialise the rest */
218 icydemux->remaining = icydemux->meta_interval;
219 icydemux->meta_remaining = 0;
225 gst_icydemux_dispose (GObject * object)
227 GstICYDemux *icydemux = GST_ICYDEMUX (object);
229 gst_icydemux_reset (icydemux);
231 G_OBJECT_CLASS (parent_class)->dispose (object);
235 gst_icydemux_add_srcpad (GstICYDemux * icydemux, GstCaps * new_caps)
237 GstPad *srcpad = NULL;
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,
246 gst_pad_set_caps (icydemux->srcpad, icydemux->src_caps);
249 /* Caps never changed */
250 gst_caps_unref (new_caps);
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);
259 gst_pad_use_fixed_caps (icydemux->srcpad);
261 if (icydemux->src_caps)
262 gst_pad_set_caps (icydemux->srcpad, icydemux->src_caps);
264 GST_DEBUG_OBJECT (icydemux, "Adding src pad with caps %" GST_PTR_FORMAT,
267 gst_pad_set_active (icydemux->srcpad, TRUE);
268 if (!(gst_element_add_pad (GST_ELEMENT (icydemux), icydemux->srcpad)))
270 gst_element_no_more_pads (GST_ELEMENT (icydemux));
277 gst_icydemux_remove_srcpad (GstICYDemux * icydemux)
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;
291 gst_icydemux_unicodify (const gchar * str)
293 const gchar *env_vars[] = { "GST_ICY_TAG_ENCODING",
294 "GST_TAG_ENCODING", NULL
297 return gst_tag_freeform_string_to_utf8 (str, -1, env_vars);
301 gst_icydemux_parse_and_send_tags (GstICYDemux * icydemux)
303 GstTagList *tags = gst_tag_list_new ();
308 gboolean found_tag = FALSE;
310 length = gst_adapter_available (icydemux->meta_adapter);
312 data = gst_adapter_peek (icydemux->meta_adapter, length);
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);
320 strings = g_strsplit (buffer, "';", 0);
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);
326 if (title && *title) {
327 gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_TITLE,
332 } else if (!g_ascii_strncasecmp (strings[i], "StreamUrl=", 10)) {
333 char *url = gst_icydemux_unicodify (strings[i] + 11);
337 gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_URL,
346 g_strfreev (strings);
348 gst_adapter_clear (icydemux->meta_adapter);
351 if (icydemux->srcpad) {
352 gst_icydemux_send_tag_event (icydemux, tags);
354 if (!icydemux->cached_tags) {
355 icydemux->cached_tags = gst_tag_list_new ();
358 gst_tag_list_insert (icydemux->cached_tags, tags,
359 GST_TAG_MERGE_REPLACE_ALL);
365 gst_icydemux_handle_event (GstPad * pad, GstEvent * event)
367 GstICYDemux *icydemux = GST_ICYDEMUX (GST_PAD_PARENT (pad));
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;
377 return gst_pad_event_default (pad, event);
379 icydemux->cached_events = g_list_append (icydemux->cached_events,
384 return gst_pad_event_default (pad, event);
389 gst_icydemux_send_cached_events (GstICYDemux * icydemux)
393 for (l = icydemux->cached_events; l != NULL; l = l->next) {
394 GstEvent *event = GST_EVENT (l->data);
396 gst_pad_push_event (icydemux->srcpad, event);
398 g_list_free (icydemux->cached_events);
399 icydemux->cached_events = NULL;
403 gst_icydemux_typefind_or_forward (GstICYDemux * icydemux, GstBuffer * buf)
405 if (icydemux->typefinding) {
408 GstTypeFindProbability prob;
410 if (icydemux->typefind_buf) {
411 icydemux->typefind_buf = gst_buffer_join (icydemux->typefind_buf, buf);
413 icydemux->typefind_buf = buf;
416 caps = gst_type_find_helper_for_buffer (GST_OBJECT (icydemux),
417 icydemux->typefind_buf, &prob);
420 if (GST_BUFFER_SIZE (icydemux->typefind_buf) < ICY_TYPE_FIND_MAX_SIZE) {
421 /* Just break for more data */
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;
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;
440 gst_caps_unref (caps);
442 if (icydemux->cached_events) {
443 gst_icydemux_send_cached_events (icydemux);
446 if (icydemux->cached_tags) {
447 gst_icydemux_send_tag_event (icydemux, icydemux->cached_tags);
448 icydemux->cached_tags = NULL;
451 /* Move onto streaming: call ourselves recursively with the typefind buffer
452 * to get that forwarded. */
453 icydemux->typefinding = FALSE;
455 tf_buf = icydemux->typefind_buf;
456 icydemux->typefind_buf = NULL;
457 return gst_icydemux_typefind_or_forward (icydemux, tf_buf);
459 if (G_UNLIKELY (icydemux->srcpad == NULL)) {
460 gst_buffer_unref (buf);
461 return GST_FLOW_ERROR;
464 buf = gst_buffer_make_metadata_writable (buf);
465 gst_buffer_set_caps (buf, icydemux->src_caps);
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;
473 return gst_pad_push (icydemux->srcpad, buf);
478 gst_icydemux_add_meta (GstICYDemux * icydemux, GstBuffer * buf)
480 if (!icydemux->meta_adapter)
481 icydemux->meta_adapter = gst_adapter_new ();
483 gst_adapter_push (icydemux->meta_adapter, buf);
487 gst_icydemux_chain (GstPad * pad, GstBuffer * buf)
489 GstICYDemux *icydemux;
490 guint size, chunk, offset;
492 GstFlowReturn ret = GST_FLOW_OK;
494 icydemux = GST_ICYDEMUX (GST_PAD_PARENT (pad));
496 if (G_UNLIKELY (icydemux->meta_interval < 0))
499 if (icydemux->meta_interval == 0) {
500 ret = gst_icydemux_typefind_or_forward (icydemux, buf);
504 /* Go through the buffer, chopping it into appropriate chunks. Forward as
505 * tags or buffers, as appropriate
507 size = GST_BUFFER_SIZE (buf);
510 if (icydemux->remaining) {
511 chunk = (size <= icydemux->remaining) ? size : icydemux->remaining;
512 sub = gst_buffer_create_sub (buf, offset, chunk);
514 icydemux->remaining -= chunk;
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)
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);
528 icydemux->meta_remaining -= chunk;
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);
536 icydemux->remaining = icydemux->meta_interval;
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).
544 icydemux->meta_remaining = 16 * GST_BUFFER_DATA (buf)[offset];
545 if (icydemux->meta_remaining == 0)
546 icydemux->remaining = icydemux->meta_interval;
554 gst_buffer_unref (buf);
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;
568 static GstStateChangeReturn
569 gst_icydemux_change_state (GstElement * element, GstStateChange transition)
571 GstStateChangeReturn ret;
572 GstICYDemux *icydemux = GST_ICYDEMUX (element);
574 ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
576 switch (transition) {
577 case GST_STATE_CHANGE_PAUSED_TO_READY:
578 gst_icydemux_reset (icydemux);
587 gst_icydemux_send_tag_event (GstICYDemux * icydemux, GstTagList * tags)
591 gst_element_post_message (GST_ELEMENT (icydemux),
592 gst_message_new_tag (GST_OBJECT (icydemux), gst_tag_list_copy (tags)));
594 event = gst_event_new_tag (tags);
595 GST_EVENT_TIMESTAMP (event) = 0;
597 GST_DEBUG_OBJECT (icydemux, "Sending tag event on src pad");
598 gst_pad_push_event (icydemux->srcpad, event);
603 plugin_init (GstPlugin * plugin)
605 GST_DEBUG_CATEGORY_INIT (icydemux_debug, "icydemux", 0,
606 "GStreamer ICY tag demuxer");
608 return gst_element_register (plugin, "icydemux",
609 GST_RANK_PRIMARY, GST_TYPE_ICYDEMUX);
612 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
615 "Demux ICY tags from a stream",
616 plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)