1 From 6c810495ff6c876e17c7eaf260959683c77450a5 Mon Sep 17 00:00:00 2001
2 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= <tim.muller@collabora.co.uk>
3 Date: Sat, 23 May 2009 00:42:01 +0100
4 Subject: [PATCH] tagreading: add tagreadbin element for fast and easy
7 Similar to playbin, just for metadata (in future maybe also thumbnailing).
11 docs/plugins/Makefile.am | 1 +
12 docs/plugins/gst-plugins-base-plugins-docs.sgml | 2 +
13 docs/plugins/gst-plugins-base-plugins-sections.txt | 17 +
14 docs/plugins/inspect/plugin-tagreading.xml | 23 +
15 gst/tagreading/Makefile.am | 28 +
16 gst/tagreading/gsttagreadbin.c | 893 ++++++++++++++++++++
17 gst/tagreading/gsttagreadbin.h | 104 +++
18 gst/tagreading/gsttagreadutils.c | 309 +++++++
19 gst/tagreading/gsttagreadutils.h | 62 ++
20 gst/tagreading/gsttagsink.c | 785 +++++++++++++++++
21 gst/tagreading/gsttagsink.h | 103 +++
22 12 files changed, 2329 insertions(+), 0 deletions(-)
23 create mode 100644 docs/plugins/inspect/plugin-tagreading.xml
24 create mode 100644 gst/tagreading/Makefile.am
25 create mode 100644 gst/tagreading/gsttagreadbin.c
26 create mode 100644 gst/tagreading/gsttagreadbin.h
27 create mode 100644 gst/tagreading/gsttagreadutils.c
28 create mode 100644 gst/tagreading/gsttagreadutils.h
29 create mode 100644 gst/tagreading/gsttagsink.c
30 create mode 100644 gst/tagreading/gsttagsink.h
32 diff --git a/configure.ac b/configure.ac
33 index 8131f8a..7a6ae5a 100644
36 @@ -430,6 +430,7 @@ AG_GST_CHECK_PLUGIN(gdp)
37 AG_GST_CHECK_PLUGIN(playback)
38 AG_GST_CHECK_PLUGIN(audioresample)
39 AG_GST_CHECK_PLUGIN(subparse)
40 +AG_GST_CHECK_PLUGIN(tagreading)
41 AG_GST_CHECK_PLUGIN(tcp)
42 AG_GST_CHECK_PLUGIN(typefind)
43 AG_GST_CHECK_PLUGIN(videotestsrc)
44 @@ -931,6 +932,7 @@ gst/gdp/Makefile
46 gst/audioresample/Makefile
48 +gst/tagreading/Makefile
51 gst/videotestsrc/Makefile
52 diff --git a/docs/plugins/Makefile.am b/docs/plugins/Makefile.am
53 index c987b85..8226d38 100644
54 --- a/docs/plugins/Makefile.am
55 +++ b/docs/plugins/Makefile.am
56 @@ -111,6 +111,7 @@ EXTRA_HFILES = \
57 $(top_srcdir)/gst/playback/gstsubtitleoverlay.h \
58 $(top_srcdir)/gst/audiorate/gstaudiorate.h \
59 $(top_srcdir)/gst/audioresample/gstaudioresample.h \
60 + $(top_srcdir)/gst/tagreading/gsttagreadbin.h \
61 $(top_srcdir)/gst/tcp/gstmultifdsink.h \
62 $(top_srcdir)/gst/tcp/gsttcpclientsrc.h \
63 $(top_srcdir)/gst/tcp/gsttcpclientsink.h \
64 diff --git a/docs/plugins/gst-plugins-base-plugins-docs.sgml b/docs/plugins/gst-plugins-base-plugins-docs.sgml
65 index ea603d6..deebd97 100644
66 --- a/docs/plugins/gst-plugins-base-plugins-docs.sgml
67 +++ b/docs/plugins/gst-plugins-base-plugins-docs.sgml
69 <xi:include href="xml/element-oggmux.xml" />
70 <xi:include href="xml/element-playbin.xml" />
71 <xi:include href="xml/element-playbin2.xml" />
72 + <xi:include href="xml/element-tagreadbin.xml" />
73 <xi:include href="xml/element-subtitleoverlay.xml" />
74 <xi:include href="xml/element-tcpclientsrc.xml" />
75 <xi:include href="xml/element-tcpclientsink.xml" />
77 <xi:include href="xml/plugin-subparse.xml" />
78 <xi:include href="xml/plugin-tcp.xml" />
79 <xi:include href="xml/plugin-theora.xml" />
80 + <xi:include href="xml/plugin-tagreading.xml" />
81 <xi:include href="xml/plugin-typefindfunctions.xml" />
82 <xi:include href="xml/plugin-uridecodebin.xml" />
83 <xi:include href="xml/plugin-video4linux.xml" />
84 diff --git a/docs/plugins/gst-plugins-base-plugins-sections.txt b/docs/plugins/gst-plugins-base-plugins-sections.txt
85 index e94c450..97dc8ca 100644
86 --- a/docs/plugins/gst-plugins-base-plugins-sections.txt
87 +++ b/docs/plugins/gst-plugins-base-plugins-sections.txt
88 @@ -504,6 +504,23 @@ gst_subtitle_overlay_get_type
92 +<FILE>element-tagreadbin</FILE>
93 +<TITLE>tagreadbin</TITLE>
95 +<SUBSECTION Standard>
97 +GST_TAG_READ_BIN_CLASS
98 +GST_TYPE_TAG_READ_BIN
100 +GST_IS_TAG_READ_BIN_CLASS
101 +GST_TAG_READ_BIN_DYN_LOCK
102 +GST_TAG_READ_BIN_DYN_UNLOCK
103 +GST_TAG_READ_BIN_SHUTDOWN_LOCK
104 +GST_TAG_READ_BIN_SHUTDOWN_UNLOCK
109 <FILE>element-tcpclientsrc</FILE>
110 <TITLE>tcpclientsrc</TITLE>
112 diff --git a/docs/plugins/inspect/plugin-tagreading.xml b/docs/plugins/inspect/plugin-tagreading.xml
114 index 0000000..6d00a48
116 +++ b/docs/plugins/inspect/plugin-tagreading.xml
119 + <name>tagreading</name>
120 + <description>Tag reading support</description>
121 + <filename>../../gst/tagreading/.libs/libgsttagreading.so</filename>
122 + <basename>libgsttagreading.so</basename>
123 + <version>0.10.22.3</version>
124 + <license>LGPL</license>
125 + <source>gst-plugins-base</source>
126 + <package>GStreamer Base Plug-ins CVS/prerelease</package>
127 + <origin>Unknown package origin</origin>
130 + <name>tagreadbin</name>
131 + <longname>Tag Read Bin</longname>
132 + <class>Generic/Bin/Metadata</class>
133 + <description>Extract metadata from an uri</description>
134 + <author>Tim-Philipp Müller <tim centricular net></author>
141 \ No newline at end of file
142 diff --git a/gst/tagreading/Makefile.am b/gst/tagreading/Makefile.am
144 index 0000000..0a0d32d
146 +++ b/gst/tagreading/Makefile.am
148 +plugindir = $(libdir)/gstreamer-@GST_MAJORMINOR@
150 +plugin_LTLIBRARIES = libgsttagreading.la
152 +libgsttagreading_la_SOURCES = \
155 + gsttagreadutils.c \
156 + gsttagreadutils.h \
159 +libgsttagreading_la_CFLAGS = \
160 + $(GST_PLUGINS_BASE_CFLAGS) \
161 + $(GST_BASE_CFLAGS) \
163 +libgsttagreading_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
164 +libgsttagreading_la_LIBADD = \
165 + $(top_builddir)/gst-libs/gst/tag/libgsttag-@GST_MAJORMINOR@.la \
166 + $(top_builddir)/gst-libs/gst/pbutils/libgstpbutils-@GST_MAJORMINOR@.la \
169 +libgsttagreading_la_LIBTOOLFLAGS = --tag=disable-static
173 + gsttagreadutils.h \
176 diff --git a/gst/tagreading/gsttagreadbin.c b/gst/tagreading/gsttagreadbin.c
178 index 0000000..d62d8eb
180 +++ b/gst/tagreading/gsttagreadbin.c
182 +/* GStreamer tag read bin
183 + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net>
185 + * This library is free software; you can redistribute it and/or
186 + * modify it under the terms of the GNU Library General Public
187 + * License as published by the Free Software Foundation; either
188 + * version 2 of the License, or (at your option) any later version.
190 + * This library is distributed in the hope that it will be useful,
191 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
192 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
193 + * Library General Public License for more details.
195 + * You should have received a copy of the GNU Library General Public
196 + * License along with this library; if not, write to the
197 + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
198 + * Boston, MA 02111-1307, USA.
202 + * SECTION:element-tagreadbin
203 + * @short_description: Extract metadata (tags and stream info)
207 + * Tagreadbin is a stand-alone element that extracts metadata (tags and
208 + * other stream info such as video resolution, audio sample rate etc.) from
209 + * files given an URI.
213 + * This element is still considered EXPERIMENTAL. Even though we don't expect
214 + * any changes, the API provided in properties, signals and bus messages may
215 + * yet change in the near future.
219 + * Tagreadbin will open the file requested and automatically plug the elements
220 + * required to extract the metadata. Only demuxers, parsers or specialised tag
221 + * reading elements (implementing the #GstTagReader interface) will be used. No
222 + * decoders will be plugged, unless video thumbnailing has been requested
223 + * (which is not implemented yet).
227 + * <title>Basic usage</title>
229 + * Tagreadbin is a #GstPipeline, so it can be used like any other GStreamer
230 + * pipeline. In particular, you can retrieve the #GstBus to watch for tag
231 + * and error messages using gst_pipeline_get_bus(). Once the
232 + * <literal>uri</literal> property has been set, the element should be
233 + * set to PLAYING state using gst_element_set_state(). TAG and ERROR messages
234 + * will then be posted on the pipeline's #GstBus soon after. If there was no
235 + * error, one or more TAG messages will be posted on the bus, followed by an
236 + * EOS message. Once an ERROR or EOS message has been received, the element
237 + * should be shut down by setting it to NULL state with gst_element_set_state().
238 + * It can then be re-used to extract metadata from another file.
242 + * <title>Tag messages</title>
244 + * Tagreadbin will collect tags from various elements and try to group them.
245 + * The first tag message posted on the bus should contain the 'global tags',
246 + * ie. tags that are valid for all sub-streams contained in the file. At the
247 + * very least, this taglist should contain information about the container
248 + * format in a #GST_TAG_CONTAINER_FORMAT tag (where applicable, otherwise it
249 + * will contain a translated string saying 'Unknown'). If there is a
250 + * #GST_TAG_DURATION tag it will contain the maximum value of all the stream
251 + * durations (as queried).
254 + * After the tag message with the global tags there will be one tag message
255 + * for each audio/video/subtitle stream, containing the information for that
256 + * particular stream. At the very least, there should be a #GST_TAG_AUDIO_CODEC,
257 + * #GST_TAG_VIDEO_CODEC, #GST_TAG_SUBITLE_CODEC, or #GST_TAG_CODEC tag. There
258 + * may also be a #GST_TAG_DURATION tag with the stream's duration.
261 + * For audio streams, there will be "channels" and "rate" tags (of type int)
262 + * in the taglist, provided that information could be extracted.
265 + * For video streams, there will be "width", "height", "pixel-aspect-ratio"
266 + * and "framerate" fields (of type int, int, fraction and fraction respectively)
267 + * if that information was available. A framerate of 0/N means that the
268 + * framerate is variable and no average framerate is known.
271 + * Just like playbin, this element does not expose any pads.
282 + * - wait with preroll until duration stabilises (e.g. for mp3/aac/mpeg)
284 + * - missing plugin messages
286 + * - Fix parser/decoder elements so that global tags / stream tags
287 + * differentiation actually works right, ie. make sure they send tags
288 + * received from upstream before pushing their own tags (we'll only consider
289 + * taglists received before the first taglist with a *_CODEC tag suitable
292 +#ifdef HAVE_CONFIG_H
296 +#include "gsttagreadbin.h"
297 +#include "gsttagreadutils.h"
298 +#include "gsttagsink.h"
300 +#include <gst/pbutils/pbutils.h>
301 +#include <gst/tag/gsttagreader.h>
305 +GST_DEBUG_CATEGORY (tagreadbin_debug);
306 +#define GST_CAT_DEFAULT tagreadbin_debug
308 +/* from gst/playback/gstplay-enum.h */
311 + GST_AUTOPLUG_SELECT_TRY,
312 + GST_AUTOPLUG_SELECT_EXPOSE,
313 + GST_AUTOPLUG_SELECT_SKIP
314 +} GstAutoplugSelectResult;
317 +#define DEFAULT_URI NULL
325 +static void gst_tag_read_bin_dispose (GObject * object);
326 +static void gst_tag_read_bin_finalize (GObject * object);
327 +static void gst_tag_read_bin_set_property (GObject * object, guint prop_id,
328 + const GValue * value, GParamSpec * spec);
329 +static void gst_tag_read_bin_get_property (GObject * object, guint prop_id,
330 + GValue * value, GParamSpec * spec);
332 +static GstStateChangeReturn gst_tag_read_bin_change_state (GstElement * element,
333 + GstStateChange transition);
334 +static void gst_tag_read_bin_handle_message (GstBin * bin, GstMessage * msg);
336 +GST_BOILERPLATE (GstTagReadBin, gst_tag_read_bin, GstPipeline,
337 + GST_TYPE_PIPELINE);
340 +gst_tag_read_bin_base_init (gpointer g_klass)
342 + /* nothing to do */
346 +gst_tag_read_bin_class_init (GstTagReadBinClass * klass)
348 + GstElementClass *gstelement_klass = (GstElementClass *) klass;
349 + GObjectClass *gobject_klass = (GObjectClass *) klass;
350 + GstBinClass *gstbin_klass = (GstBinClass *) klass;
352 + gst_element_class_set_details_simple (gstelement_klass,
353 + "Tag Read Bin", "Generic/Bin/Metadata", "Extract metadata from an uri",
354 + "Tim-Philipp Müller <tim centricular net>");
356 + gobject_klass->set_property = gst_tag_read_bin_set_property;
357 + gobject_klass->get_property = gst_tag_read_bin_get_property;
359 + gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_tag_read_bin_dispose);
360 + gobject_klass->finalize = GST_DEBUG_FUNCPTR (gst_tag_read_bin_finalize);
363 + * GstTagReadBin:uri
365 + * URI to extract metadata from. Only file:// URIs are supported at the
366 + * moment. To create an URI from a filename, you can use g_filename_to_uri().
368 + g_object_class_install_property (gobject_klass, PROP_URI,
369 + g_param_spec_string ("uri", "URI", "URI to extract tags from",
370 + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
372 + gstelement_klass->change_state =
373 + GST_DEBUG_FUNCPTR (gst_tag_read_bin_change_state);
375 + gstbin_klass->handle_message =
376 + GST_DEBUG_FUNCPTR (gst_tag_read_bin_handle_message);
380 +gst_tag_read_bin_factory_is_demuxer (GstElementFactory * factory)
382 + const gchar *klass = gst_element_factory_get_klass (factory);
384 + return (klass != NULL && strstr (klass, "Demux") != NULL);
388 +gst_tag_read_bin_factory_is_parser (GstElementFactory * factory)
390 + const gchar *klass = gst_element_factory_get_klass (factory);
392 + return (klass != NULL && strstr (klass, "Parse") != NULL);
396 +gst_tag_read_bin_factory_is_metadata_extractor (GstElementFactory * factory)
398 + const gchar *klass = gst_element_factory_get_klass (factory);
400 + return (klass && strstr (klass, "Metadata") && strstr (klass, "Extracter"));
404 +gst_tag_read_bin_factory_is_tag_reader (GstElementFactory * factory)
407 + const gchar *klass;
409 + klass = gst_element_factory_get_klass (factory);
411 + /* only demuxers, parsers or metadata extractor elements can play
412 + * (note: in parcticular, we don't want to plug decodebin2 even though
413 + * it does implement the tag reader interface as well) */
414 + if (!gst_tag_read_bin_factory_is_demuxer (factory) &&
415 + !gst_tag_read_bin_factory_is_parser (factory) &&
416 + !gst_tag_read_bin_factory_is_metadata_extractor (factory)) {
420 + /* tag readers are fine whatever their rank */
421 + if (gst_element_factory_has_interface (factory, "GstTagReader")) {
422 + GST_LOG ("selecting %s, it implements the tag reader interface",
423 + GST_PLUGIN_FEATURE_NAME (factory));
427 + /* only select elements with autoplugging rank */
428 + rank = gst_plugin_feature_get_rank (GST_PLUGIN_FEATURE (factory));
429 + if (rank < GST_RANK_MARGINAL)
432 + GST_LOG ("selecting %s, class/rank ok", GST_PLUGIN_FEATURE_NAME (factory));
436 +/* function used to sort element features. We first sort on the tag reader
437 + * interface, then rank, then on the element name (to get a consistent,
438 + * predictable list) */
440 +gst_tag_read_bin_compare_ranks (GValue * v1, GValue * v2)
442 + const gchar *rname1, *rname2;
443 + GstPluginFeature *f1, *f2;
444 + gboolean is_tagreader1, is_tagreader2;
447 + f1 = g_value_get_object (v1);
448 + f2 = g_value_get_object (v2);
450 + is_tagreader1 = gst_element_factory_has_interface (GST_ELEMENT_FACTORY (f1),
452 + is_tagreader2 = gst_element_factory_has_interface (GST_ELEMENT_FACTORY (f2),
455 + if (is_tagreader1 != is_tagreader2)
456 + return (is_tagreader2) ? 1 : -1;
458 + diff = gst_plugin_feature_get_rank (f2) - gst_plugin_feature_get_rank (f1);
462 + rname1 = gst_plugin_feature_get_name (f1);
463 + rname2 = gst_plugin_feature_get_name (f2);
465 + diff = strcmp (rname2, rname1);
470 +static GValueArray *
471 +gst_tag_read_bin_get_tag_readers (void)
473 + GValueArray *result;
476 + result = g_value_array_new (0);
478 + /* get the feature list using the filter */
479 + list = gst_registry_get_feature_list (gst_registry_get_default (),
480 + GST_TYPE_ELEMENT_FACTORY);
482 + /* filter and convert to an array */
483 + for (l = list; l != NULL; l = l->next) {
484 + GstElementFactory *factory = GST_ELEMENT_FACTORY (l->data);
486 + if (gst_tag_read_bin_factory_is_tag_reader (factory)) {
487 + GValue val = { 0, };
489 + g_value_init (&val, GST_TYPE_ELEMENT_FACTORY);
490 + g_value_set_object (&val, factory);
491 + g_value_array_append (result, &val);
492 + g_value_unset (&val);
496 + gst_plugin_feature_list_free (list);
498 + /* sort on tag reader interface rank and name */
499 + g_value_array_sort (result, (GCompareFunc) gst_tag_read_bin_compare_ranks);
505 +gst_tag_read_bin_factory_list_has_demuxer (GValueArray * factories)
509 + for (i = 0; i < factories->n_values; ++i) {
510 + GstElementFactory *factory;
513 + val = g_value_array_get_nth (factories, i);
514 + factory = GST_ELEMENT_FACTORY (g_value_get_object (val));
516 + if (gst_tag_read_bin_factory_is_demuxer (factory))
524 +gst_tag_read_bin_init (GstTagReadBin * tbin, GstTagReadBinClass * klass)
526 + tbin->dyn_lock = g_mutex_new ();
528 + /* cache interesting element factories */
529 + tbin->elements = gst_tag_read_bin_get_tag_readers ();
533 +gst_tag_read_bin_dispose (GObject * object)
535 + GstTagReadBin *tbin = GST_TAG_READ_BIN (object);
537 + g_value_array_free (tbin->elements);
539 + G_OBJECT_CLASS (parent_class)->dispose (object);
543 +gst_tag_read_bin_finalize (GObject * object)
545 + GstTagReadBin *tbin = GST_TAG_READ_BIN (object);
547 + g_mutex_free (tbin->dyn_lock);
548 + tbin->dyn_lock = NULL;
549 + g_free (tbin->uri);
552 + G_OBJECT_CLASS (parent_class)->finalize (object);
556 +gst_tag_read_bin_post_tags (GstTagReadBin * tbin)
561 + GST_DEBUG_OBJECT (tbin, "posting tags");
563 + tags = gst_tag_sink_get_global_tags (GST_TAG_SINK (tbin->tagsink));
564 + GST_INFO_OBJECT (tbin, "global tags: %" GST_PTR_FORMAT, tags);
565 + if (tags != NULL) {
566 + /* if there's no container format in the tags, add one based on caps */
567 + if (!gst_tag_read_utils_list_has_container_tag (tags) &&
568 + tbin->container_caps != NULL) {
569 + gst_tag_read_utils_add_container_tag_from_caps (tags,
570 + tbin->container_caps);
573 + GST_BIN_CLASS (parent_class)->handle_message (GST_BIN (tbin),
574 + gst_message_new_tag (GST_OBJECT_CAST (tbin), tags));
577 + for (i = 0;; ++i) {
578 + tags = gst_tag_sink_get_stream_tags (GST_TAG_SINK (tbin->tagsink), i);
580 + /* there should always at least be a *_CODEC tag based on the caps */
584 + GST_BIN_CLASS (parent_class)->handle_message (GST_BIN (tbin),
585 + gst_message_new_tag (GST_OBJECT_CAST (tbin), tags));
590 +gst_tag_read_bin_handle_message (GstBin * bin, GstMessage * msg)
592 + GstTagReadBin *tbin = GST_TAG_READ_BIN (bin);
594 + /* Suppress all tag messages from other elements. We'll collect tags from
595 + * the pads and then post messages of our own when we're done and we have
596 + * all the information */
597 + switch (GST_MESSAGE_TYPE (msg)) {
598 + case GST_MESSAGE_TAG:
600 + GST_LOG_OBJECT (bin, "suppressing tags from element %s: %" GST_PTR_FORMAT,
601 + GST_OBJECT_NAME (msg->src), msg->structure);
602 + GST_OBJECT_LOCK (tbin);
603 + tbin->tag_messages = g_list_append (tbin->tag_messages, msg);
605 + GST_OBJECT_UNLOCK (tbin);
608 + case GST_MESSAGE_ASYNC_DONE:
610 + if (msg->src == GST_OBJECT_CAST (tbin->tagsink)) {
611 + GST_DEBUG_OBJECT (bin, "done collecting tags");
612 + gst_tag_read_bin_post_tags (tbin);
621 + GST_BIN_CLASS (parent_class)->handle_message (bin, msg);
626 +gst_tag_read_bin_set_uri (GstTagReadBin * tbin, const gchar * uri)
628 + if (uri == NULL || !gst_uri_is_valid (uri)) {
629 + g_warning ("Can't set invalid URI '%s' on tagreadbin element %s",
630 + GST_STR_NULL (uri), GST_ELEMENT_NAME (tbin));
634 + GST_FIXME_OBJECT (tbin, "Add support for non-file:// URIs");
636 + if (!gst_uri_has_protocol (uri, "file")) {
637 + g_warning ("Tagreadbin only supports file:// URIs for the time being");
641 + GST_OBJECT_LOCK (tbin);
642 + if (tbin->uri != uri) {
643 + g_free (tbin->uri);
644 + tbin->uri = g_strdup (uri);
646 + GST_OBJECT_UNLOCK (tbin);
648 + GST_INFO_OBJECT (tbin, "Set URI to '%s'", uri);
652 +gst_tag_read_bin_set_property (GObject * object, guint prop_id,
653 + const GValue * value, GParamSpec * pspec)
655 + GstTagReadBin *tbin = GST_TAG_READ_BIN (object);
659 + gst_tag_read_bin_set_uri (tbin, g_value_get_string (value));
662 + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
668 +gst_tag_read_bin_get_property (GObject * object, guint prop_id, GValue * value,
669 + GParamSpec * pspec)
671 + GstTagReadBin *tbin = GST_TAG_READ_BIN (object);
675 + GST_OBJECT_LOCK (tbin);
676 + g_value_set_string (value, tbin->uri);
677 + GST_OBJECT_UNLOCK (tbin);
680 + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
685 +/* This function is called when a new pad is added to uridecodebin. */
687 +tag_read_bin_decodebin_pad_added (GstElement * decodebin, GstPad * pad,
688 + GstTagReadBin * tbin)
690 + GstPadLinkReturn res;
691 + GstPad *request_pad;
693 + GST_TAG_READ_BIN_SHUTDOWN_LOCK (tbin, shutdown);
695 + request_pad = gst_element_get_request_pad (tbin->tagsink, "sink%d");
696 + GST_LOG_OBJECT (request_pad, "got new request pad");
697 + tbin->request_pads = g_list_prepend (tbin->request_pads, request_pad);
699 + res = gst_pad_link (pad, request_pad);
701 + if (GST_PAD_LINK_SUCCESSFUL (res)) {
702 + GST_DEBUG_OBJECT (tbin, "linked new pad %s:%s to %s:%s",
703 + GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (request_pad));
705 + GST_WARNING_OBJECT (tbin, "failed to link new pad %s:%s to %s:%s: %d",
706 + GST_DEBUG_PAD_NAME (pad), GST_DEBUG_PAD_NAME (request_pad), res);
709 + GST_TAG_READ_BIN_SHUTDOWN_UNLOCK (tbin);
714 + GST_DEBUG_OBJECT (tbin, "shutdown");
719 +/* we get called when all pads are available and we must connect the sinks to
721 + * The main purpose of the code is to see if we have video/audio and subtitles
722 + * and pick the right pipelines to display them.
724 + * The selectors installed on the group tell us about the presence of
725 + * audio/video and subtitle streams. This allows us to see if we need
726 + * visualisation, video or/and audio.
729 +tag_read_bin_decodebin_no_more_pads (GstElement * decodebin,
730 + GstTagReadBin * tbin)
732 + GST_DEBUG_OBJECT (decodebin, "no more pads");
735 +static GValueArray *
736 +gst_factory_list_filter_by_sinkcaps (GValueArray * array, const GstCaps * caps)
738 + GValueArray *result;
741 + result = g_value_array_new (0);
743 + GST_DEBUG ("finding factories for %" GST_PTR_FORMAT, caps);
745 + /* loop over all the factories */
746 + for (i = 0; i < array->n_values; i++) {
748 + GstElementFactory *factory;
749 + const GList *templates;
752 + value = g_value_array_get_nth (array, i);
753 + factory = g_value_get_object (value);
755 + /* get the templates from the element factory */
756 + templates = gst_element_factory_get_static_pad_templates (factory);
757 + for (walk = (GList *) templates; walk; walk = g_list_next (walk)) {
758 + GstStaticPadTemplate *templ = walk->data;
760 + /* we only care about the sink templates */
761 + if (templ->direction == GST_PAD_SINK) {
762 + GstCaps *intersect;
763 + GstCaps *tmpl_caps;
765 + /* try to intersect the caps with the caps of the template */
766 + tmpl_caps = gst_static_caps_get (&templ->static_caps);
768 + /* FIXME, intersect is not the right method, we ideally want to check
769 + * for a subset here */
770 + intersect = gst_caps_intersect (caps, tmpl_caps);
771 + gst_caps_unref (tmpl_caps);
773 + /* check if the intersection is empty */
774 + if (!gst_caps_is_empty (intersect)) {
775 + /* non empty intersection, we can use this element */
776 + GValue resval = { 0, };
777 + g_value_init (&resval, G_TYPE_OBJECT);
778 + g_value_set_object (&resval, factory);
779 + g_value_array_append (result, &resval);
780 + g_value_unset (&resval);
781 + gst_caps_unref (intersect);
784 + gst_caps_unref (intersect);
788 + GST_DEBUG ("found %u factories for %" GST_PTR_FORMAT, result->n_values, caps);
792 +/* Called when we must provide a list of factories to plug pad with caps */
793 +static GValueArray *
794 +tag_read_bin_decodebin_autoplug_factories (GstElement * decodebin,
795 + GstPad * pad, GstCaps * caps, GstTagReadBin * tbin)
797 + GValueArray *result = NULL;
799 + GST_TAG_READ_BIN_SHUTDOWN_LOCK (tbin, shutdown);
801 + GST_DEBUG_OBJECT (tbin, "getting factories for caps %" GST_PTR_FORMAT, caps);
803 + /* filter out the elements based on the caps */
804 + result = gst_factory_list_filter_by_sinkcaps (tbin->elements, caps);
806 + if (tbin->container_caps == NULL &&
807 + gst_tag_read_bin_factory_list_has_demuxer (result)) {
808 + GST_DEBUG_OBJECT (tbin, "possible container caps: %" GST_PTR_FORMAT, caps);
809 + gst_caps_replace (&tbin->container_caps, caps);
812 + GST_DEBUG_OBJECT (tbin, "found %u factories", result->n_values);
814 + /* nothing else found => expose pad as it is now and hope for the best */
815 + if (result->n_values == 0) {
816 + if (!gst_tag_read_utils_caps_complete (caps)) {
817 + GST_FIXME ("caps without all the info we want and no more tagreaders/"
818 + "parsers to plug for %" GST_PTR_FORMAT, caps);
820 + g_value_array_free (result);
824 + GST_TAG_READ_BIN_SHUTDOWN_UNLOCK (tbin);
830 + GST_DEBUG_OBJECT (tbin, "shutdown");
836 +tag_read_bin_decodebin_autoplug_continue (GstElement * decodebin, GstPad * pad,
837 + GstCaps * caps, GstTagReadBin * tbin)
839 + GstFormat fmt = GST_FORMAT_TIME;
840 + gboolean do_continue = FALSE;
841 + gboolean query_ok, capsinfo_ok;
844 + GST_TAG_READ_BIN_SHUTDOWN_LOCK (tbin, shutdown);
846 + GST_DEBUG_OBJECT (tbin, "continue with caps %" GST_PTR_FORMAT "?", caps);
848 + query_ok = gst_pad_query_duration (pad, &fmt, &dur) && (dur > 0);
849 + GST_LOG_OBJECT (tbin, "duration query: res=%d, duration=%" G_GINT64_FORMAT,
852 + capsinfo_ok = gst_tag_read_utils_caps_complete (caps);
853 + GST_LOG_OBJECT (tbin, "caps info complete: %d", capsinfo_ok);
855 + /* stop if we have both a duration and the caps are complete enough */
856 + do_continue = !query_ok || !capsinfo_ok;
857 + GST_LOG_OBJECT (tbin, "continue: %d", do_continue);
859 + GST_TAG_READ_BIN_SHUTDOWN_UNLOCK (tbin);
860 + return do_continue;
864 + GST_DEBUG_OBJECT (tbin, "shutting down");
869 +/* We are asked to select an element. We can return TRY or EXPOSE */
870 +static GstAutoplugSelectResult
871 +tag_read_bin_decodebin_autoplug_select (GstElement * decodebin, GstPad * pad,
872 + GstCaps * caps, GstElementFactory * factory, GstTagReadBin * tbin)
874 + GST_LOG_OBJECT (tbin, "selecting %s for caps %" GST_PTR_FORMAT,
875 + GST_PLUGIN_FEATURE_NAME (factory), caps);
877 + return GST_AUTOPLUG_SELECT_TRY;
880 +#define REMOVE_SIGNAL(obj,id) \
882 + g_signal_handler_disconnect (obj, id); \
887 +gst_tag_read_bin_cleanup (GstTagReadBin * tbin)
889 + while (tbin->request_pads) {
890 + GstPad *pad = tbin->request_pads->data;
892 + gst_element_release_request_pad (tbin->tagsink, pad);
893 + gst_object_unref (pad);
894 + tbin->request_pads =
895 + g_list_delete_link (tbin->request_pads, tbin->request_pads);
897 + if (tbin->uridecodebin) {
898 + GST_LOG_OBJECT (tbin, "removing existing decodebin");
899 + REMOVE_SIGNAL (tbin->uridecodebin, tbin->pad_added_id);
900 + REMOVE_SIGNAL (tbin->uridecodebin, tbin->no_more_pads_id);
901 + REMOVE_SIGNAL (tbin->uridecodebin, tbin->autoplug_factories_id);
902 + REMOVE_SIGNAL (tbin->uridecodebin, tbin->autoplug_select_id);
903 + gst_element_set_state (tbin->uridecodebin, GST_STATE_NULL);
904 + gst_bin_remove (GST_BIN (tbin), tbin->uridecodebin);
905 + tbin->uridecodebin = NULL;
906 + GST_DEBUG_OBJECT (tbin, "removed decodebin");
908 + if (tbin->tagsink) {
909 + GST_LOG_OBJECT (tbin, "removing tagsink");
910 + gst_element_set_state (tbin->tagsink, GST_STATE_NULL);
911 + gst_bin_remove (GST_BIN (tbin), tbin->tagsink);
912 + tbin->tagsink = NULL;
913 + GST_DEBUG_OBJECT (tbin, "removed tagsink");
915 + while (tbin->tag_messages) {
916 + gst_message_unref (tbin->tag_messages->data);
917 + tbin->tag_messages = g_list_delete_link (tbin->tag_messages,
918 + tbin->tag_messages);
920 + gst_caps_replace (&tbin->container_caps, NULL);
924 +gst_tag_read_bin_setup (GstTagReadBin * tbin)
926 + GstStateChangeReturn state_ret;
929 + tbin->container_caps = NULL;
931 + GST_DEBUG_OBJECT (tbin, "creating new decodebin");
932 + tbin->uridecodebin = gst_element_factory_make ("uridecodebin", NULL);
933 + if (tbin->uridecodebin == NULL)
936 + gst_tag_reader_set_tag_reading_mode (GST_TAG_READER (tbin->uridecodebin),
937 + GST_TAG_READING_MODE_TAGS_ONLY);
939 + gst_bin_add (GST_BIN_CAST (tbin), tbin->uridecodebin);
941 + /* pad-added: we connect the newly exposed pad to the tagsink */
942 + tbin->pad_added_id = g_signal_connect (tbin->uridecodebin, "pad-added",
943 + G_CALLBACK (tag_read_bin_decodebin_pad_added), tbin);
945 + /* no-more-pads: we just connect this for logging purposes */
946 + tbin->no_more_pads_id = g_signal_connect (tbin->uridecodebin, "no-more-pads",
947 + G_CALLBACK (tag_read_bin_decodebin_no_more_pads), tbin);
949 + /* autoplug-factories: will be called when a new media type is found. We
950 + * return a list of tagreaders or parser for decodebin to try */
951 + tbin->autoplug_factories_id =
952 + g_signal_connect (tbin->uridecodebin, "autoplug-factories",
953 + G_CALLBACK (tag_read_bin_decodebin_autoplug_factories), tbin);
955 + /* autoplug-continue: determine whether we've got the kind of caps we're
956 + * looking for etc. or if we want to plug further tagreaders/parsers */
957 + tbin->autoplug_continue_id = g_signal_connect (tbin->uridecodebin,
958 + "autoplug-continue",
959 + G_CALLBACK (tag_read_bin_decodebin_autoplug_continue), tbin);
961 + /* autoplug-select: report what's being plugged next */
962 + tbin->autoplug_select_id = g_signal_connect (tbin->uridecodebin,
963 + "autoplug-select", G_CALLBACK (tag_read_bin_decodebin_autoplug_select),
966 + g_object_set (tbin->uridecodebin, "uri", tbin->uri, NULL);
968 + GST_STATE_LOCK (tbin);
969 + pending = GST_STATE_PENDING (tbin);
970 + GST_STATE_UNLOCK (tbin);
972 + tbin->tagsink = g_object_new (GST_TYPE_TAG_SINK, NULL);
973 + gst_bin_add (GST_BIN (tbin), tbin->tagsink);
974 + gst_element_set_state (tbin->tagsink, pending);
976 + state_ret = gst_element_set_state (tbin->uridecodebin, pending);
977 + if (state_ret == GST_STATE_CHANGE_FAILURE)
978 + goto decodebin_failure;
985 + gst_element_post_message (GST_ELEMENT_CAST (tbin),
986 + gst_missing_element_message_new (GST_ELEMENT (tbin), "uridecodebin"));
987 + GST_ELEMENT_ERROR (tbin, CORE, MISSING_PLUGIN, (NULL), ("no uridecodebin"));
992 + GST_DEBUG_OBJECT (tbin, "decodebin state change failed");
993 + gst_tag_read_bin_cleanup (tbin);
994 + /* an error message should have been posted already, but better be safe */
995 + GST_ELEMENT_ERROR (tbin, CORE, STATE_CHANGE, (NULL), (NULL));
1000 +static GstStateChangeReturn
1001 +gst_tag_read_bin_change_state (GstElement * element, GstStateChange transition)
1003 + GstStateChangeReturn ret;
1004 + GstTagReadBin *tbin;
1006 + tbin = GST_TAG_READ_BIN (element);
1008 + GST_LOG_OBJECT (tbin, "transition %s -> %s",
1009 + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
1010 + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
1012 + switch (transition) {
1013 + case GST_STATE_CHANGE_READY_TO_PAUSED:
1014 + GST_LOG_OBJECT (tbin, "clearing shutdown flag");
1015 + g_atomic_int_set (&tbin->shutdown, 0);
1016 + if (!gst_tag_read_bin_setup (tbin))
1017 + return GST_STATE_CHANGE_FAILURE;
1019 + case GST_STATE_CHANGE_PAUSED_TO_READY:
1020 + GST_LOG_OBJECT (tbin, "setting shutdown flag");
1021 + g_atomic_int_set (&tbin->shutdown, 1);
1023 + /* wait for all callbacks to end by taking the lock.
1024 + * No dynamic (critical) new callbacks will
1025 + * be able to happen as we set the shutdown flag. */
1026 + GST_TAG_READ_BIN_DYN_LOCK (tbin);
1027 + GST_LOG_OBJECT (tbin, "dynamic lock taken, we can continue shutdown");
1028 + GST_TAG_READ_BIN_DYN_UNLOCK (tbin);
1030 + case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
1031 + gst_element_post_message (element,
1032 + gst_message_new_eos (GST_OBJECT_CAST (element)));
1034 + case GST_STATE_CHANGE_READY_TO_NULL:
1040 + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1041 + if (ret == GST_STATE_CHANGE_FAILURE)
1044 + switch (transition) {
1045 + case GST_STATE_CHANGE_PAUSED_TO_READY:
1046 + case GST_STATE_CHANGE_READY_TO_NULL:
1047 + gst_tag_read_bin_cleanup (tbin);
1053 + GST_LOG_OBJECT (tbin, "transition %s -> %s, returning %s",
1054 + gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
1055 + gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)),
1056 + gst_element_state_change_return_get_name (ret));
1062 +plugin_init (GstPlugin * plugin)
1064 + GST_DEBUG_CATEGORY_INIT (tagreadbin_debug, "tagreadbin", 0, "Tag read bin");
1066 + gst_tag_read_utils_register_caps_tags ();
1068 + return gst_element_register (plugin, "tagreadbin", GST_RANK_NONE,
1069 + GST_TYPE_TAG_READ_BIN);
1072 +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
1073 + "tagreading", "Tag reading support", plugin_init, VERSION, GST_LICENSE,
1074 + GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
1075 diff --git a/gst/tagreading/gsttagreadbin.h b/gst/tagreading/gsttagreadbin.h
1076 new file mode 100644
1077 index 0000000..d380bce
1079 +++ b/gst/tagreading/gsttagreadbin.h
1081 +/* GStreamer tag read bin
1082 + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net>
1084 + * This library is free software; you can redistribute it and/or
1085 + * modify it under the terms of the GNU Library General Public
1086 + * License as published by the Free Software Foundation; either
1087 + * version 2 of the License, or (at your option) any later version.
1089 + * This library is distributed in the hope that it will be useful,
1090 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1091 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1092 + * Library General Public License for more details.
1094 + * You should have received a copy of the GNU Library General Public
1095 + * License along with this library; if not, write to the
1096 + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
1097 + * Boston, MA 02111-1307, USA.
1100 +#ifndef __GST_TAG_READ_BIN_H__
1101 +#define __GST_TAG_READ_BIN_H__
1103 +#include <gst/gst.h>
1107 +#define GST_TYPE_TAG_READ_BIN (gst_tag_read_bin_get_type())
1108 +#define GST_TAG_READ_BIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TAG_READ_BIN,GstTagReadBin))
1109 +#define GST_TAG_READ_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TAG_READ_BIN,GstTagReadBinClass))
1110 +#define GST_IS_TAG_READ_BIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TAG_READ_BIN))
1111 +#define GST_IS_TAG_READ_BIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TAG_READ_BIN))
1113 +#define GST_TAG_READ_BIN_DYN_LOCK(tbin) g_mutex_lock ((tbin)->dyn_lock)
1114 +#define GST_TAG_READ_BIN_DYN_UNLOCK(tbin) g_mutex_unlock ((tbin)->dyn_lock)
1116 +/* lock for shutdown */
1117 +#define GST_TAG_READ_BIN_SHUTDOWN_LOCK(tbin,label) \
1119 + if (G_UNLIKELY (g_atomic_int_get (&tbin->shutdown))) \
1121 + GST_TAG_READ_BIN_DYN_LOCK (tbin); \
1122 + if (G_UNLIKELY (g_atomic_int_get (&tbin->shutdown))) { \
1123 + GST_TAG_READ_BIN_DYN_UNLOCK (tbin); \
1128 +/* unlock for shutdown */
1129 +#define GST_TAG_READ_BIN_SHUTDOWN_UNLOCK(bin) \
1130 + GST_TAG_READ_BIN_DYN_UNLOCK (bin); \
1132 +typedef struct _GstTagReadBin GstTagReadBin;
1133 +typedef struct _GstTagReadBinClass GstTagReadBinClass;
1138 + * tagreadbin element structure (opaque)
1141 +struct _GstTagReadBin
1143 + GstPipeline parent;
1146 + GstElement * uridecodebin;
1149 + GstElement * tagsink;
1151 + /* factories we can use for selecting elements */
1152 + GValueArray * elements;
1154 + /* lock protecting dynamic adding/removing */
1155 + GMutex * dyn_lock;
1157 + /* if we are shutting down or not */
1158 + gint shutdown; /* ATOMIC */
1160 + /* tag messages we dropped (to compare to tag events collected from pads) */
1161 + GList *tag_messages; /* OBJECT_LOCK */
1163 + GstCaps *container_caps; /* DYN_LOCK */
1165 + GList *request_pads; /* DYN_LOCK */
1167 + /* decodebin signals */
1168 + gulong pad_added_id;
1169 + gulong no_more_pads_id;
1170 + gulong autoplug_factories_id;
1171 + gulong autoplug_continue_id;
1172 + gulong autoplug_select_id;
1175 +struct _GstTagReadBinClass
1177 + GstPipelineClass parent_class;
1180 +GType gst_tag_read_bin_get_type (void);
1184 +#endif /* __GST_TAG_READ_BIN_H__ */
1185 diff --git a/gst/tagreading/gsttagreadutils.c b/gst/tagreading/gsttagreadutils.c
1186 new file mode 100644
1187 index 0000000..acaebb6
1189 +++ b/gst/tagreading/gsttagreadutils.c
1191 +/* GStreamer tag read bin utilities
1192 + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net>
1194 + * This library is free software; you can redistribute it and/or
1195 + * modify it under the terms of the GNU Library General Public
1196 + * License as published by the Free Software Foundation; either
1197 + * version 2 of the License, or (at your option) any later version.
1199 + * This library is distributed in the hope that it will be useful,
1200 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1201 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1202 + * Library General Public License for more details.
1204 + * You should have received a copy of the GNU Library General Public
1205 + * License along with this library; if not, write to the
1206 + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
1207 + * Boston, MA 02111-1307, USA.
1210 +#include "gsttagreadutils.h"
1211 +#include <gst/pbutils/pbutils.h>
1213 +GST_DEBUG_CATEGORY_EXTERN (tagreadbin_debug);
1214 +#define GST_CAT_DEFAULT tagreadbin_debug
1216 +GstTagReadStreamType
1217 +gst_tag_read_utils_get_caps_type (const GstCaps * caps)
1220 + const gchar *name;
1222 + s = gst_caps_get_structure (caps, 0);
1223 + name = gst_structure_get_name (s);
1225 + if (g_str_equal (name, "video/x-dvd-subpicture") ||
1226 + g_str_has_prefix (name, "text/")) {
1227 + return GST_TAG_READ_STREAM_TYPE_SUBTITLE;
1230 + if (g_str_has_prefix (name, "application/x-subtitle"))
1231 + return GST_TAG_READ_STREAM_TYPE_SUBTITLE;
1233 + if (g_str_has_prefix (name, "audio/"))
1234 + return GST_TAG_READ_STREAM_TYPE_AUDIO;
1236 + if (g_str_has_prefix (name, "video/") || g_str_has_prefix (name, "image/")) {
1237 + return GST_TAG_READ_STREAM_TYPE_VIDEO;
1240 + return GST_TAG_READ_STREAM_TYPE_UNKNOWN;
1244 +gst_tag_read_utils_caps_complete (const GstCaps * caps)
1247 + const gchar *name;
1248 + gboolean complete;
1250 + s = gst_caps_get_structure (caps, 0);
1251 + name = gst_structure_get_name (s);
1253 + if (g_str_equal (name, "video/x-dvd-subpicture") ||
1254 + g_str_has_prefix (name, "text/")) {
1256 + } else if (g_str_has_prefix (name, "application/x-subtitle")) {
1257 + /* subtitle parser might give us more info */
1259 + } else if (g_str_has_prefix (name, "audio/")) {
1260 + complete = gst_structure_has_field (s, "channels") &&
1261 + gst_structure_has_field (s, "rate");
1262 + } else if (g_str_has_prefix (name, "video/") ||
1263 + g_str_has_prefix (name, "image/")) {
1264 + complete = gst_structure_has_field (s, "width") &&
1265 + gst_structure_has_field (s, "height");
1274 +gst_tag_read_utils_add_codec_tag_from_caps (GstTagList * tags,
1275 + const GstCaps * caps)
1277 + GstTagReadStreamType type;
1278 + const gchar *tag_name, *tag_name_unknown;
1280 + gst_pb_utils_init ();
1282 + type = gst_tag_read_utils_get_caps_type (caps);
1284 + case GST_TAG_READ_STREAM_TYPE_AUDIO:
1285 + tag_name = GST_TAG_AUDIO_CODEC;
1286 + tag_name_unknown = GST_TAG_AUDIO_CODEC "-unknown";
1288 + case GST_TAG_READ_STREAM_TYPE_VIDEO:
1289 + tag_name = GST_TAG_VIDEO_CODEC;
1290 + tag_name_unknown = GST_TAG_VIDEO_CODEC "-unknown";
1292 + case GST_TAG_READ_STREAM_TYPE_SUBTITLE:
1293 + tag_name = GST_TAG_SUBTITLE_CODEC;
1294 + tag_name_unknown = GST_TAG_SUBTITLE_CODEC "-unknown";
1297 + tag_name = GST_TAG_CODEC;
1298 + tag_name_unknown = GST_TAG_CODEC "-unknown";
1302 + GST_LOG ("Adding %s field to tags from %" GST_PTR_FORMAT, tag_name, caps);
1303 + gst_pb_utils_add_codec_description_to_tag_list (tags, tag_name, caps);
1305 + if (!gst_tag_read_utils_list_has_codec_tag (tags)) {
1306 + GST_FIXME ("Couldn't get codec name for caps %" GST_PTR_FORMAT, caps);
1307 + if (!gst_tag_exists (tag_name_unknown)) {
1308 + gst_tag_register (tag_name_unknown, GST_TAG_FLAG_ENCODED,
1309 + G_TYPE_BOOLEAN, "codec unknown",
1310 + "TRUE if the codec is not known and the *-codec "
1311 + "tag contains the string 'unknown' or somesuch", NULL);
1313 + /* FIXME: translate */
1314 + gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, tag_name,
1315 + "Unknown", tag_name_unknown, TRUE, NULL);
1320 +/* FIXME: remove once we depend on 0.10.24 */
1321 +#ifndef GST_TAG_CONTAINER_FORMAT
1322 +#define GST_TAG_CONTAINER_FORMAT "container-format"
1326 +gst_tag_read_utils_add_container_tag_from_caps (GstTagList * tags,
1327 + const GstCaps * caps)
1329 + gst_pb_utils_init ();
1331 + /* FIXME: remove once we depend on 0.10.24 */
1332 + if (!gst_tag_exists (GST_TAG_CONTAINER_FORMAT)) {
1333 + gst_tag_register (GST_TAG_CONTAINER_FORMAT, GST_TAG_FLAG_ENCODED,
1334 + G_TYPE_STRING, "container format", "container format", NULL);
1337 + GST_LOG ("Adding container-format field to tags from %" GST_PTR_FORMAT, caps);
1338 + gst_pb_utils_add_codec_description_to_tag_list (tags,
1339 + GST_TAG_CONTAINER_FORMAT, caps);
1341 + /* Adding bogus container-format tag */
1342 + if (!gst_tag_read_utils_list_has_container_tag (tags)) {
1343 + GST_FIXME ("Couldn't get container format for caps %" GST_PTR_FORMAT, caps);
1345 + if (!gst_tag_exists ("container-format-unknown")) {
1346 + gst_tag_register ("container-format-unknown", GST_TAG_FLAG_ENCODED,
1347 + G_TYPE_BOOLEAN, "container format unknown",
1348 + "TRUE if the container format is not known and the contaier-format "
1349 + "tag contains the string 'unknown' or somesuch", NULL);
1352 + /* FIXME: translate */
1353 + gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_CONTAINER_FORMAT,
1354 + "Unknown", "container-format-unknown", TRUE, NULL);
1359 +gst_tag_read_utils_list_has_container_tag (GstTagList * tags)
1363 + v = gst_tag_list_get_value_index (tags, GST_TAG_CONTAINER_FORMAT, 0);
1364 + return (v != NULL);
1368 +gst_tag_read_utils_list_has_codec_tag (GstTagList * tags)
1370 + return (gst_tag_list_get_value_index (tags, GST_TAG_CODEC, 0) != NULL) ||
1371 + (gst_tag_list_get_value_index (tags, GST_TAG_VIDEO_CODEC, 0) != NULL) ||
1372 + (gst_tag_list_get_value_index (tags, GST_TAG_AUDIO_CODEC, 0) != NULL);
1376 +gst_tag_read_utils_list_merge_field (GstTagList * tags, GstStructure * s,
1377 + const gchar * field, GType expected_type)
1381 + if ((v = gst_structure_get_value (s, field))) {
1382 + if (G_VALUE_TYPE (v) == expected_type) {
1383 + if (gst_tag_list_get_value_index (tags, field, 0) != NULL) {
1384 + GST_WARNING ("overwriting tag '%s' with value from caps", field);
1386 + gst_tag_list_add_values (tags, GST_TAG_MERGE_REPLACE, field, v, NULL);
1387 + GST_LOG ("added field '%s' to taglist %" GST_PTR_FORMAT, field, tags);
1389 + GST_WARNING ("field '%s' in caps %" GST_PTR_FORMAT " does not have the "
1390 + "expected type (%s)", field, s, g_type_name (expected_type));
1393 + GST_LOG ("no field '%s' in caps %" GST_PTR_FORMAT, field, s);
1398 +gst_tag_read_utils_register_caps_tags (void)
1401 + if (!gst_tag_exists ("channels")) {
1402 + gst_tag_register ("channels", GST_TAG_FLAG_ENCODED,
1403 + G_TYPE_INT, "channels", "number of audio channels", NULL);
1405 + if (!gst_tag_exists ("rate")) {
1406 + gst_tag_register ("rate", GST_TAG_FLAG_ENCODED,
1407 + G_TYPE_INT, "rate", "sample rate of audio", NULL);
1410 + /* video/image tags */
1411 + if (!gst_tag_exists ("width")) {
1412 + gst_tag_register ("width", GST_TAG_FLAG_ENCODED,
1413 + G_TYPE_INT, "width", "width of video or image", NULL);
1415 + if (!gst_tag_exists ("height")) {
1416 + gst_tag_register ("height", GST_TAG_FLAG_ENCODED,
1417 + G_TYPE_INT, "height", "height of video or image", NULL);
1419 + if (!gst_tag_exists ("framerate")) {
1420 + gst_tag_register ("framerate", GST_TAG_FLAG_ENCODED,
1421 + GST_TYPE_FRACTION, "framerate", "framerate of video", NULL);
1423 + if (!gst_tag_exists ("pixel-aspect-ratio")) {
1424 + gst_tag_register ("pixel-aspect-ratio", GST_TAG_FLAG_ENCODED,
1425 + GST_TYPE_FRACTION, "pixel-aspect-ratio", "PAR of video", NULL);
1430 +gst_tag_read_utils_list_add_caps_info (GstTagList * tags, const GstCaps * caps)
1432 + GstTagReadStreamType type;
1438 + gst_tag_read_utils_register_caps_tags ();
1440 + s = gst_caps_get_structure (caps, 0);
1442 + type = gst_tag_read_utils_get_caps_type (caps);
1444 + /* keep fields in sync with gst_tag_read_utils_register_caps_tags() */
1445 + case GST_TAG_READ_STREAM_TYPE_AUDIO:
1446 + gst_tag_read_utils_list_merge_field (tags, s, "channels", G_TYPE_INT);
1447 + gst_tag_read_utils_list_merge_field (tags, s, "rate", G_TYPE_INT);
1449 + /* keep fields in sync with gst_tag_read_utils_register_caps_tags() */
1450 + case GST_TAG_READ_STREAM_TYPE_VIDEO:
1451 + gst_tag_read_utils_list_merge_field (tags, s, "width", G_TYPE_INT);
1452 + gst_tag_read_utils_list_merge_field (tags, s, "height", G_TYPE_INT);
1453 + gst_tag_read_utils_list_merge_field (tags, s, "framerate",
1454 + GST_TYPE_FRACTION);
1455 + gst_tag_read_utils_list_merge_field (tags, s, "pixel-aspect-ratio",
1456 + GST_TYPE_FRACTION);
1458 + case GST_TAG_READ_STREAM_TYPE_SUBTITLE:
1466 +gst_tag_read_utils_list_add_duration (GstTagList * tags, GstClockTime dur)
1468 + if (GST_CLOCK_TIME_IS_VALID (dur) && dur > 0) {
1469 + gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_DURATION, dur, NULL);
1470 + GST_LOG ("added field 'duration' to taglist %" GST_PTR_FORMAT, tags);
1472 + GST_DEBUG ("not adding 'duration' field to taglist, duration not valid");
1477 +taglist_remove_private_foreach_func (const GstTagList * list,
1478 + const gchar * tag, gpointer user_data)
1480 + GList **p_list = user_data;
1482 + /* tag is a quark string, we don't need to worry about it getting freed */
1483 + if (g_str_has_prefix (tag, "private-"))
1484 + *p_list = g_list_prepend (*p_list, (gpointer) tag);
1488 +gst_tag_read_utils_list_remove_private_tags (GstTagList * tags)
1490 + GList *list = NULL;
1492 + gst_tag_list_foreach (tags, taglist_remove_private_foreach_func, &list);
1493 + while (list != NULL) {
1494 + gst_tag_list_remove_tag (tags, list->data);
1495 + GST_LOG ("Removed tag %s, taglist now %" GST_PTR_FORMAT,
1496 + (const char *) list->data, tags);
1497 + list = g_list_delete_link (list, list);
1500 diff --git a/gst/tagreading/gsttagreadutils.h b/gst/tagreading/gsttagreadutils.h
1501 new file mode 100644
1502 index 0000000..06ce9ea
1504 +++ b/gst/tagreading/gsttagreadutils.h
1506 +/* GStreamer tag read bin utilities
1507 + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net>
1509 + * This library is free software; you can redistribute it and/or
1510 + * modify it under the terms of the GNU Library General Public
1511 + * License as published by the Free Software Foundation; either
1512 + * version 2 of the License, or (at your option) any later version.
1514 + * This library is distributed in the hope that it will be useful,
1515 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1516 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1517 + * Library General Public License for more details.
1519 + * You should have received a copy of the GNU Library General Public
1520 + * License along with this library; if not, write to the
1521 + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
1522 + * Boston, MA 02111-1307, USA.
1525 +#ifndef __GST_TAG_READ_UTILS_H__
1526 +#define __GST_TAG_READ_UTILS_H__
1528 +#include <gst/gst.h>
1534 + GST_TAG_READ_STREAM_TYPE_UNKNOWN = 0,
1535 + GST_TAG_READ_STREAM_TYPE_AUDIO,
1536 + GST_TAG_READ_STREAM_TYPE_VIDEO,
1537 + GST_TAG_READ_STREAM_TYPE_SUBTITLE
1538 +} GstTagReadStreamType;
1540 +GstTagReadStreamType gst_tag_read_utils_get_caps_type (const GstCaps * caps);
1542 +gboolean gst_tag_read_utils_caps_complete (const GstCaps * caps);
1544 +void gst_tag_read_utils_add_codec_tag_from_caps (GstTagList * tags,
1545 + const GstCaps * caps);
1547 +void gst_tag_read_utils_add_container_tag_from_caps (GstTagList * tags,
1548 + const GstCaps * caps);
1550 +gboolean gst_tag_read_utils_list_has_codec_tag (GstTagList * tags);
1552 +gboolean gst_tag_read_utils_list_has_container_tag (GstTagList * tags);
1555 +void gst_tag_read_utils_register_caps_tags (void);
1557 +void gst_tag_read_utils_list_add_caps_info (GstTagList * tags,
1558 + const GstCaps * caps);
1560 +void gst_tag_read_utils_list_add_duration (GstTagList * tags,
1561 + GstClockTime dur);
1563 +void gst_tag_read_utils_list_remove_private_tags (GstTagList * tags);
1567 +#endif /* __GST_TAG_UTILS_H__ */
1568 diff --git a/gst/tagreading/gsttagsink.c b/gst/tagreading/gsttagsink.c
1569 new file mode 100644
1570 index 0000000..3741897
1572 +++ b/gst/tagreading/gsttagsink.c
1574 +/* GStreamer tag read bin sink
1575 + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net>
1577 + * This library is free software; you can redistribute it and/or
1578 + * modify it under the terms of the GNU Library General Public
1579 + * License as published by the Free Software Foundation; either
1580 + * version 2 of the License, or (at your option) any later version.
1582 + * This library is distributed in the hope that it will be useful,
1583 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1584 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1585 + * Library General Public License for more details.
1587 + * You should have received a copy of the GNU Library General Public
1588 + * License along with this library; if not, write to the
1589 + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
1590 + * Boston, MA 02111-1307, USA.
1593 +/* Sink with N request sink pads; collects caps and tags on each stream
1594 + * until we have seen a buffer or EOS event on all streams. Once we've
1595 + * collected all the data we want, the sink "prerolls".
1597 + * We derive from basesink to avoid having to implement async state change
1598 + * behaviour ourselves, which is quite tricky to get right.
1600 + * Of course we could have done all this very differently, eg. use one sink per
1601 + * stream or use buffer probes in the tagreadbin etc., but having a separate
1602 + * element that handles all the collection logic seems conceptually nicer.
1603 + * If http://bugzilla.gnome.org/show_bug.cgi?id=342155 gets implemented,
1604 + * we could just derive one tagsink per stream and then use that API.
1605 + * (we currently don't even need this, but we might in the future, e.g.
1606 + * should we want to preroll only once the duration returned is somewhat
1607 + * stable, or if we want to get a thumbnail of the N-th keyframe or so.)
1610 +#ifdef HAVE_CONFIG_H
1611 +#include "config.h"
1614 +#include <gst/gst-i18n-plugin.h>
1616 +#include "gsttagsink.h"
1617 +#include "gsttagreadutils.h"
1618 +#include <string.h>
1620 +/* after this many buffers we mark a stream as done even if haven't gotten
1621 + * all the information we're looking for yet (caps, duration etc.) */
1622 +#define GST_TAG_SINK_MAX_BUFFERS 90
1624 +GST_DEBUG_CATEGORY_EXTERN (tagreadbin_debug);
1625 +#define GST_CAT_DEFAULT tagreadbin_debug
1627 +static GstStaticPadTemplate sinkn_template =
1628 +GST_STATIC_PAD_TEMPLATE ("sink%d", GST_PAD_SINK,
1629 + GST_PAD_REQUEST, GST_STATIC_CAPS_ANY);
1630 +static GstStaticPadTemplate sink_template =
1631 +GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK,
1632 + GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY);
1634 +static void gst_tag_sink_dispose (GObject * object);
1635 +static void gst_tag_sink_finalize (GObject * object);
1636 +static gboolean gst_tag_sink_start (GstBaseSink * basesink);
1637 +static gboolean gst_tag_sink_stop (GstBaseSink * basesink);
1638 +static gboolean gst_tag_sink_unlock (GstBaseSink * basesink);
1639 +static GstPad *gst_tag_sink_request_pad (GstElement * element,
1640 + GstPadTemplate * templ, const gchar * unused);
1641 +static void gst_tag_sink_release_pad (GstElement * element, GstPad * pad);
1642 +static gboolean gst_tag_sink_sink_event (GstPad * pad, GstEvent * event);
1643 +static gboolean gst_tag_sink_setcaps (GstPad * pad, GstCaps * caps);
1645 +GST_BOILERPLATE (GstTagSink, gst_tag_sink, GstBaseSink, GST_TYPE_BASE_SINK);
1648 +gst_tag_sink_base_init (gpointer g_klass)
1650 + /* nothing to do */
1654 +gst_tag_sink_class_init (GstTagSinkClass * klass)
1656 + GstBaseSinkClass *basesink_klass = (GstBaseSinkClass *) klass;
1657 + GstElementClass *element_klass = (GstElementClass *) klass;
1658 + GObjectClass *gobject_klass = (GObjectClass *) klass;
1660 + /* dummy template for the normal base sink "sink" pad, which is never
1661 + * actually linked, we only use it to force basesink to preroll for us
1662 + * when we're ready */
1663 + gst_element_class_add_pad_template (element_klass,
1664 + gst_static_pad_template_get (&sink_template));
1666 + /* template for the request pads, which are used */
1667 + gst_element_class_add_pad_template (element_klass,
1668 + gst_static_pad_template_get (&sinkn_template));
1670 + gobject_klass->dispose = GST_DEBUG_FUNCPTR (gst_tag_sink_dispose);
1671 + gobject_klass->finalize = GST_DEBUG_FUNCPTR (gst_tag_sink_finalize);
1673 + element_klass->release_pad = GST_DEBUG_FUNCPTR (gst_tag_sink_release_pad);
1674 + element_klass->request_new_pad = GST_DEBUG_FUNCPTR (gst_tag_sink_request_pad);
1676 + basesink_klass->start = GST_DEBUG_FUNCPTR (gst_tag_sink_start);
1677 + basesink_klass->stop = GST_DEBUG_FUNCPTR (gst_tag_sink_stop);
1678 + basesink_klass->unlock = GST_DEBUG_FUNCPTR (gst_tag_sink_unlock);
1682 +gst_tag_sink_init (GstTagSink * tagsink, GstTagSinkClass * klass)
1684 + g_static_rec_mutex_init (&tagsink->streams_lock);
1685 + tagsink->streams = g_array_new (FALSE, FALSE, sizeof (GstTagSinkStream));
1687 + gst_base_sink_set_async_enabled (GST_BASE_SINK (tagsink), TRUE);
1688 + gst_base_sink_set_sync (GST_BASE_SINK (tagsink), FALSE);
1692 +gst_tag_sink_dispose (GObject * object)
1694 + GstTagSink *tagsink = GST_TAG_SINK (object);
1696 + if (tagsink->streams != NULL) {
1697 + if (tagsink->streams->len > 0) {
1698 + g_warning ("GstTagSink: %u unreleased pads in ::dispose()",
1699 + tagsink->streams->len);
1701 + /* just leak the contents of any unreleased streams (shouldn't happen) */
1702 + g_array_free (tagsink->streams, TRUE);
1703 + tagsink->streams = NULL;
1706 + G_OBJECT_CLASS (parent_class)->dispose (object);
1710 +gst_tag_sink_finalize (GObject * object)
1712 + GstTagSink *tagsink = GST_TAG_SINK (object);
1714 + g_static_rec_mutex_free (&tagsink->streams_lock);
1716 + if (tagsink->global_tags) {
1717 + gst_tag_list_free (tagsink->global_tags);
1718 + tagsink->global_tags = NULL;
1720 + G_OBJECT_CLASS (parent_class)->finalize (object);
1723 +/* must be called with STREAMS_LOCK held */
1725 +gst_tag_sink_stream_query_duration (GstTagSinkStream * stream)
1727 + GstFormat fmt = GST_FORMAT_TIME;
1731 + res = gst_pad_query_peer_duration (stream->pad, &fmt, &dur) && (dur > 0);
1733 + /* this likely indicates a bug in some upstream element */
1734 + if (!res && GST_CLOCK_TIME_IS_VALID (stream->duration))
1735 + GST_ERROR_OBJECT (stream->pad, "previous duration good, now we got none");
1737 + stream->duration = dur;
1739 + GST_LOG_OBJECT (stream->pad, "duration: %" GST_TIME_FORMAT,
1740 + GST_TIME_ARGS (dur));
1746 +gst_tag_sink_stream_is_done (GstTagSinkStream * stream)
1748 + /* we're definitely done if we've seen an EOS */
1749 + if (stream->got_eos) {
1750 + /* warn if we didn't get anything before the EOS */
1751 + if (stream->last_buffer == NULL)
1752 + GST_WARNING_OBJECT (stream->pad, "EOS without any buffer");
1753 + if (!gst_tag_sink_stream_query_duration (stream))
1754 + GST_WARNING_OBJECT (stream->pad, "EOS and duration query failed");
1758 + if (stream->last_buffer == NULL) {
1759 + GST_LOG_OBJECT (stream->pad, "no buffer yet");
1763 + /* bail out after this many buffers */
1764 + if (stream->num_buffers >= GST_TAG_SINK_MAX_BUFFERS) {
1765 + GST_WARNING_OBJECT (stream->pad, "Marking stream as done after %u buffers,"
1766 + " even if we don't have caps and/or duration yet", stream->num_buffers);
1770 + if (stream->caps == NULL) {
1771 + GST_LOG_OBJECT (stream->pad, "got buffer, but no caps!");
1775 + if (!gst_tag_sink_stream_query_duration (stream)) {
1776 + GST_LOG_OBJECT (stream->pad, "no duration yet");
1783 +/* must be called with STREAMS_LOCK held */
1785 +gst_tag_sink_check_preroll (GstTagSink * tagsink, GstTagSinkStream * stream)
1787 + /* have we prerolled already? */
1788 + if (!tagsink->async_pending)
1791 + /* is this stream done? */
1792 + if (!gst_tag_sink_stream_is_done (stream)) {
1793 + GST_LOG_OBJECT (stream->pad, "stream not done yet");
1797 + if (!stream->done) {
1798 + stream->done = TRUE;
1799 + --tagsink->num_streams_pending;
1800 + GST_INFO_OBJECT (stream->pad, "stream is done now, %u more pending",
1801 + tagsink->num_streams_pending);
1804 + if (tagsink->num_streams_pending > 0) {
1805 + GST_LOG_OBJECT (tagsink, "still %u streams pending",
1806 + tagsink->num_streams_pending);
1810 + GST_INFO_OBJECT (tagsink, "info on all sink pads complete, preroll");
1812 + tagsink->async_pending = FALSE;
1814 + /* just send an EOS event on basesink's sink pad to make us preroll. We
1815 + * can't use gst_base_sink_do_preroll() because it won't set the internal
1816 + * EOS flag right, which will make it do a _wait_for_preroll() when going
1817 + * back from PLAYING to PAUSED.
1818 + * If you think this is ugly, try deriving from GstElement and implement
1819 + * async state change behaviour in a correct and race-free way. */
1820 + GST_LOG_OBJECT (tagsink, "pushing EOS event on sinkpad to make us preroll");
1821 + gst_pad_send_event (GST_BASE_SINK_PAD (tagsink),
1822 + gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, 0, -1, 0));
1823 + gst_pad_send_event (GST_BASE_SINK_PAD (tagsink), gst_event_new_eos ());
1824 + GST_LOG_OBJECT (tagsink, "pushed EOS");
1828 +/* must be called with the STREAMS_LOCK held */
1829 +static GstTagSinkStream *
1830 +gst_tag_sink_get_stream_from_pad (GstTagSink * tagsink, GstPad * pad)
1834 + for (i = 0; i < tagsink->streams->len; ++i) {
1835 + GstTagSinkStream *s;
1837 + s = &g_array_index (tagsink->streams, GstTagSinkStream, i);
1838 + if (s->pad == pad)
1846 +gst_tag_sink_setcaps (GstPad * pad, GstCaps * caps)
1848 + GstTagSinkStream *s;
1849 + GstTagSink *tagsink;
1851 + tagsink = GST_TAG_SINK (GST_PAD_PARENT (pad));
1853 + GST_INFO_OBJECT (pad, "caps: %" GST_PTR_FORMAT, caps);
1855 + GST_TAG_SINK_STREAMS_LOCK (tagsink);
1856 + s = gst_tag_sink_get_stream_from_pad (tagsink, pad);
1857 + gst_caps_replace (&s->caps, caps);
1858 + GST_TAG_SINK_STREAMS_UNLOCK (tagsink);
1864 +gst_tag_sink_sink_event (GstPad * pad, GstEvent * event)
1866 + GstTagSink *tagsink;
1868 + tagsink = GST_TAG_SINK (GST_PAD_PARENT (pad));
1870 + GST_LOG_OBJECT (pad, "%s event: %" GST_PTR_FORMAT,
1871 + GST_EVENT_TYPE_NAME (event), event->structure);
1873 + GST_TAG_SINK_SHUTDOWN_LOCK (tagsink, shutdown);
1875 + switch (GST_EVENT_TYPE (event)) {
1876 + case GST_EVENT_EOS:
1878 + GstTagSinkStream *s;
1880 + s = gst_tag_sink_get_stream_from_pad (tagsink, pad);
1881 + if (!s->got_eos) {
1882 + s->got_eos = TRUE;
1883 + gst_tag_sink_check_preroll (tagsink, s);
1887 + case GST_EVENT_TAG:
1889 + GstTagSinkStream *s;
1890 + GstTagList *taglist = NULL;
1892 + gst_event_parse_tag (event, &taglist);
1894 + s = gst_tag_sink_get_stream_from_pad (tagsink, pad);
1895 + taglist = gst_tag_list_copy (taglist);
1896 + gst_tag_read_utils_list_remove_private_tags (taglist);
1897 + s->tags = g_list_append (s->tags, taglist);
1904 + GST_TAG_SINK_STREAMS_UNLOCK (tagsink);
1905 + return gst_pad_event_default (pad, event);
1909 + GST_DEBUG_OBJECT (tagsink, "shutting down");
1914 +static GstFlowReturn
1915 +gst_tag_sink_chain (GstPad * pad, GstBuffer * buf)
1917 + GstTagSinkStream *s;
1918 + GstFlowReturn flow;
1919 + GstTagSink *tagsink;
1921 + tagsink = GST_TAG_SINK (GST_PAD_PARENT (pad));
1923 + GST_TAG_SINK_SHUTDOWN_LOCK (tagsink, shutdown);
1925 + s = gst_tag_sink_get_stream_from_pad (tagsink, pad);
1927 + /* if we're done already, just skip straight to the preroll check */
1929 + goto check_preroll;
1931 + if (s->caps == NULL)
1932 + GST_ERROR_OBJECT (pad, "received buffer without caps");
1935 + gst_buffer_replace (&s->last_buffer, buf);
1936 + GST_LOG_OBJECT (pad, "buffer %d", s->num_buffers);
1940 + if (gst_tag_sink_check_preroll (tagsink, s)) {
1941 + gst_pad_push_event (pad, gst_event_new_flush_start ());
1942 + flow = GST_FLOW_WRONG_STATE;
1944 + flow = GST_FLOW_OK;
1947 + GST_TAG_SINK_STREAMS_UNLOCK (tagsink);
1951 + gst_buffer_unref (buf);
1956 + GST_DEBUG_OBJECT (tagsink, "shutting down");
1957 + flow = GST_FLOW_WRONG_STATE;
1963 +gst_tag_sink_request_pad (GstElement * element, GstPadTemplate * templ,
1964 + const gchar * unused)
1966 + GstTagSinkStream stream = { NULL, };
1967 + GstTagSink *tagsink;
1972 + if (templ->direction != GST_PAD_SINK)
1975 + tagsink = GST_TAG_SINK (element);
1977 + /* increment pad counter */
1978 + padcount = g_atomic_int_exchange_and_add (&tagsink->padcount, 1);
1980 + name = g_strdup_printf ("sink%d", padcount);
1981 + pad = gst_pad_new_from_template (templ, name);
1982 + GST_DEBUG_OBJECT (tagsink, "request new pad %s, got pad %p", name, pad);
1985 + gst_pad_set_setcaps_function (pad, GST_DEBUG_FUNCPTR (gst_tag_sink_setcaps));
1986 + gst_pad_set_event_function (pad, GST_DEBUG_FUNCPTR (gst_tag_sink_sink_event));
1987 + gst_pad_set_chain_function (pad, GST_DEBUG_FUNCPTR (gst_tag_sink_chain));
1989 + gst_pad_set_active (pad, TRUE);
1991 + stream.pad = gst_object_ref (pad);
1992 + stream.duration = GST_CLOCK_TIME_NONE;
1993 + stream.done = FALSE;
1995 + /* takes ownership of the pad */
1996 + if (!gst_element_add_pad (element, pad))
1997 + goto could_not_add;
1999 + GST_TAG_SINK_STREAMS_LOCK (tagsink);
2000 + ++tagsink->num_streams_pending;
2001 + g_array_append_vals (tagsink->streams, &stream, 1);
2002 + GST_TAG_SINK_STREAMS_UNLOCK (tagsink);
2004 + GST_INFO_OBJECT (stream.pad, "added pad");
2005 + return stream.pad;
2010 + g_warning ("GstTagSink: requested new pad that is not a SINK pad!");
2015 + GST_DEBUG_OBJECT (element, "could not add pad %s", GST_PAD_NAME (pad));
2016 + gst_object_unref (pad);
2022 +gst_tag_sink_release_pad (GstElement * element, GstPad * pad)
2024 + GstTagSinkStream stream = { NULL, };
2025 + GstTagSink *tagsink;
2028 + tagsink = GST_TAG_SINK (element);
2029 + GST_DEBUG_OBJECT (tagsink, "release pad %s", GST_PAD_NAME (pad));
2031 + GST_TAG_SINK_STREAMS_LOCK (tagsink);
2032 + for (i = 0; i < tagsink->streams->len; ++i) {
2033 + GstTagSinkStream *s;
2035 + s = &g_array_index (tagsink->streams, GstTagSinkStream, i);
2036 + if (s->pad == pad) {
2038 + g_array_remove_index (tagsink->streams, i);
2043 + if (stream.pad != NULL && !stream.done)
2044 + --tagsink->num_streams_pending;
2046 + GST_TAG_SINK_STREAMS_UNLOCK (tagsink);
2048 + if (stream.pad == NULL) {
2049 + GST_ERROR_OBJECT (tagsink, "pad %s:%s to release not found in pad list!",
2050 + GST_DEBUG_PAD_NAME (pad));
2052 + GST_INFO_OBJECT (stream.pad, "releasing pad");
2053 + gst_caps_replace (&stream.caps, NULL);
2054 + g_list_foreach (stream.tags, (GFunc) gst_tag_list_free, NULL);
2055 + g_list_free (stream.tags);
2056 + stream.tags = NULL;
2057 + if (stream.stream_tags) {
2058 + gst_tag_list_free (stream.stream_tags);
2059 + stream.stream_tags = NULL;
2061 + gst_pad_set_active (stream.pad, FALSE);
2062 + gst_element_remove_pad (element, stream.pad);
2063 + gst_object_unref (stream.pad);
2064 + stream.pad = NULL;
2065 + gst_buffer_replace (&stream.last_buffer, NULL);
2066 + GST_INFO_OBJECT (tagsink, "released pad");
2071 +gst_tag_sink_start (GstBaseSink * basesink)
2073 + GstTagSink *tagsink = GST_TAG_SINK (basesink);
2075 + GST_LOG ("here we go");
2076 + tagsink->async_pending = TRUE;
2077 + tagsink->num_streams_pending = 0;
2082 +gst_tag_sink_unlock (GstBaseSink * basesink)
2084 + GstTagSink *tagsink = GST_TAG_SINK (basesink);
2086 + GST_LOG_OBJECT (tagsink, "setting shutdown flag");
2087 + g_atomic_int_set (&tagsink->shutdown, 1);
2092 +gst_tag_sink_stop (GstBaseSink * basesink)
2094 + GstTagSink *tagsink = GST_TAG_SINK (basesink);
2096 + g_atomic_int_set (&tagsink->shutdown, 1);
2097 + /* wait for all pad callbacks to end by taking the lock.
2098 + * No dynamic (critical) new callbacks will
2099 + * be able to happen as we set the shutdown flag. */
2100 + GST_LOG_OBJECT (tagsink, "trying to acquire streams lock");
2101 + GST_TAG_SINK_STREAMS_LOCK (tagsink);
2102 + GST_LOG_OBJECT (tagsink, "streams lock taken, we can continue shutdown");
2103 + GST_TAG_SINK_STREAMS_UNLOCK (tagsink);
2107 +/* methods used by tagreadbin */
2109 +/* FIXME: make API in core to test structures/taglists for equality public */
2111 +tag_lists_are_equal (GstTagList * tags1, GstTagList * tags2)
2113 + GstCaps *caps1, *caps2;
2116 + /* not very efficient, but simple .. */
2117 + caps1 = gst_caps_new_full (gst_structure_copy (tags1), NULL);
2118 + caps2 = gst_caps_new_full (gst_structure_copy (tags2), NULL);
2119 + res = gst_caps_is_equal (caps1, caps2);
2120 + gst_caps_unref (caps1);
2121 + gst_caps_unref (caps2);
2125 +/* must be called with STREAMS_LOCK held */
2127 +gst_tag_sink_stream_find_tags (GstTagSinkStream * stream, GstTagList * tags)
2131 + for (l = stream->tags; l != NULL; l = l->next) {
2132 + if (tag_lists_are_equal (l->data, tags))
2139 +/* must be called with STREAMS_LOCK held */
2141 +gst_tag_sink_remove_tags_from_all_streams (GstTagSink * tsink,
2142 + GstTagList * global_tags)
2147 + copy = gst_tag_list_copy (global_tags);
2148 + for (i = 0; i < tsink->streams->len; ++i) {
2149 + GstTagSinkStream *stream;
2152 + stream = &g_array_index (tsink->streams, GstTagSinkStream, i);
2153 + if ((node = gst_tag_sink_stream_find_tags (stream, copy))) {
2154 + gst_tag_list_free (node->data);
2155 + stream->tags = g_list_delete_link (stream->tags, node);
2158 + gst_tag_list_free (copy);
2161 +/* must be called with STREAMS_LOCK held */
2163 +gst_tag_sink_stream_has_tags (GstTagSinkStream * stream, GstTagList * tags)
2165 + return (gst_tag_sink_stream_find_tags (stream, tags) != NULL);
2168 +/* must be called with STREAMS_LOCK held */
2170 +gst_tag_sink_tags_are_global (GstTagSink * tsink, GstTagList * tags)
2172 + gboolean res = TRUE;
2175 + /* Are the given tags common to all streams? */
2176 + for (i = 0; i < tsink->streams->len; ++i) {
2177 + GstTagSinkStream *stream;
2179 + stream = &g_array_index (tsink->streams, GstTagSinkStream, i);
2180 + if (!gst_tag_sink_stream_has_tags (stream, tags)) {
2186 + GST_LOG_OBJECT (tsink, "tags %" GST_PTR_FORMAT " are %s", tags,
2187 + (res) ? "global" : "not global");
2192 +/* Try to determine 'global tags' common to all streams. Must be called with
2193 + * STREAMS_LOCK held. */
2195 +gst_tag_sink_find_global_tags (GstTagSink * tsink)
2197 + GList *l, *global_lists = NULL;
2198 + GstClockTime max_dur = 0;
2201 + if (tsink->global_tags != NULL)
2206 + /* Find all tags that are common to all streams */
2207 + for (i = 0; i < tsink->streams->len; ++i) {
2208 + GstTagSinkStream *stream;
2210 + stream = &g_array_index (tsink->streams, GstTagSinkStream, i);
2211 + for (l = stream->tags; l != NULL; l = l->next) {
2212 + GstTagList *tags = l->data;
2214 + /* only tags received before a taglist with a *_CODEC tag are global */
2215 + if (gst_tag_read_utils_list_has_codec_tag (tags))
2218 + if (gst_tag_sink_tags_are_global (tsink, tags)) {
2219 + GST_DEBUG_OBJECT (tsink, "global tags: %" GST_PTR_FORMAT, tags);
2220 + global_lists = g_list_prepend (global_lists, gst_tag_list_copy (tags));
2221 + gst_tag_sink_remove_tags_from_all_streams (tsink, tags);
2227 + global_lists = g_list_reverse (global_lists);
2229 + if (global_lists == NULL)
2230 + goto no_global_tags;
2232 + /* merge all global tags into one tag list */
2233 + tsink->global_tags = gst_tag_list_new ();
2234 + while ((l = global_lists)) {
2235 + gst_tag_list_insert (tsink->global_tags, l->data, GST_TAG_MERGE_APPEND);
2236 + gst_tag_list_free (l->data);
2237 + global_lists = g_list_delete_link (global_lists, l);
2242 + /* Find biggest duration of all streams */
2244 + for (i = 0; i < tsink->streams->len; ++i) {
2245 + GstTagSinkStream *stream;
2247 + stream = &g_array_index (tsink->streams, GstTagSinkStream, i);
2248 + if (GST_CLOCK_TIME_IS_VALID (stream->duration))
2249 + max_dur = MAX (stream->duration, max_dur);
2252 + gst_tag_read_utils_list_add_duration (tsink->global_tags, max_dur);
2254 + GST_INFO_OBJECT (tsink, "global tags: %" GST_PTR_FORMAT, tsink->global_tags);
2259 + GST_LOG_OBJECT (tsink, "couldn't find any global tags");
2260 + tsink->global_tags = gst_tag_list_new ();
2267 +gst_tag_sink_get_global_tags (GstTagSink * tsink)
2269 + GstTagList *ret = NULL;
2271 + GST_TAG_SINK_STREAMS_LOCK (tsink);
2273 + gst_tag_sink_find_global_tags (tsink);
2275 + if (tsink->global_tags != NULL) {
2276 + ret = gst_tag_list_copy (tsink->global_tags);
2278 + ret = gst_tag_list_new ();
2281 + GST_TAG_SINK_STREAMS_UNLOCK (tsink);
2286 +/* Postprocess stream tags. Must be called with the STREAMS_LOCK held */
2288 +gst_tag_sink_stream_analyse_tags (GstTagSink * tsink, GstTagSinkStream * stream)
2292 + /* have we done all of this before? */
2293 + if (stream->stream_tags != NULL)
2296 + /* remove global tags first if we haven't done that yet */
2297 + gst_tag_sink_find_global_tags (tsink);
2299 + /* merge all remaining tags into one tag list */
2300 + stream->stream_tags = gst_tag_list_new ();
2301 + while ((l = stream->tags)) {
2302 + gst_tag_list_insert (stream->stream_tags, l->data, GST_TAG_MERGE_APPEND);
2303 + gst_tag_list_free (l->data);
2304 + stream->tags = g_list_delete_link (stream->tags, l);
2307 + /* if we don't have a codec tag for this stream, add one based on the caps */
2308 + if (!gst_tag_read_utils_list_has_codec_tag (stream->stream_tags)) {
2309 + gst_tag_read_utils_add_codec_tag_from_caps (stream->stream_tags,
2313 + /* merge interesting stuff from tags into the tags */
2314 + gst_tag_read_utils_list_add_caps_info (stream->stream_tags, stream->caps);
2316 + /* merge duration into the tags */
2317 + gst_tag_read_utils_list_add_duration (stream->stream_tags, stream->duration);
2319 + GST_INFO_OBJECT (tsink, "stream tags: %" GST_PTR_FORMAT, stream->stream_tags);
2322 +/* will return an empty tag list for 'no tags' and NULL if no such stream
2323 + * (although there should always be at least a codec tag based on the caps) */
2325 +gst_tag_sink_get_stream_tags (GstTagSink * tsink, guint num)
2327 + GstTagSinkStream *stream;
2330 + GST_TAG_SINK_STREAMS_LOCK (tsink);
2332 + if (num >= tsink->streams->len) {
2337 + stream = &g_array_index (tsink->streams, GstTagSinkStream, num);
2339 + if (stream->stream_tags != NULL) {
2340 + ret = gst_tag_list_copy (stream->stream_tags);
2344 + gst_tag_sink_stream_analyse_tags (tsink, stream);
2346 + if (stream->stream_tags != NULL) {
2347 + ret = gst_tag_list_copy (stream->stream_tags);
2349 + ret = gst_tag_list_new ();
2350 + GST_LOG_OBJECT (tsink, "no stream tags for stream %u", num);
2355 + GST_TAG_SINK_STREAMS_UNLOCK (tsink);
2359 diff --git a/gst/tagreading/gsttagsink.h b/gst/tagreading/gsttagsink.h
2360 new file mode 100644
2361 index 0000000..adf5ff3
2363 +++ b/gst/tagreading/gsttagsink.h
2365 +/* GStreamer tag read bin sink
2366 + * Copyright (C) 2009 Tim-Philipp Müller <tim centricular net>
2368 + * This library is free software; you can redistribute it and/or
2369 + * modify it under the terms of the GNU Library General Public
2370 + * License as published by the Free Software Foundation; either
2371 + * version 2 of the License, or (at your option) any later version.
2373 + * This library is distributed in the hope that it will be useful,
2374 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2375 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
2376 + * Library General Public License for more details.
2378 + * You should have received a copy of the GNU Library General Public
2379 + * License along with this library; if not, write to the
2380 + * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
2381 + * Boston, MA 02111-1307, USA.
2384 +#ifndef __GST_TAG_SINK_H__
2385 +#define __GST_TAG_SINK_H__
2387 +#include <gst/gst.h>
2388 +#include <gst/base/gstbasesink.h>
2392 +#define GST_TYPE_TAG_SINK (gst_tag_sink_get_type())
2393 +#define GST_TAG_SINK(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TAG_SINK,GstTagSink))
2394 +#define GST_TAG_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TAG_SINK,GstTagSinkClass))
2395 +#define GST_IS_TAG_SINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_TAG_SINK))
2396 +#define GST_IS_TAG_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_TAG_SINK))
2398 +#define GST_TAG_SINK_STREAMS_LOCK(tsink) g_static_rec_mutex_lock (&tsink->streams_lock)
2399 +#define GST_TAG_SINK_STREAMS_UNLOCK(tsink) g_static_rec_mutex_unlock (&tsink->streams_lock)
2401 +/* lock for shutdown */
2402 +#define GST_TAG_SINK_SHUTDOWN_LOCK(tsink,label) \
2404 + if (G_UNLIKELY (g_atomic_int_get (&tsink->shutdown))) \
2406 + GST_TAG_SINK_STREAMS_LOCK (tsink); \
2407 + if (G_UNLIKELY (g_atomic_int_get (&tsink->shutdown))) { \
2408 + GST_TAG_SINK_STREAMS_UNLOCK (tsink); \
2413 +/* unlock for shutdown */
2414 +#define GST_TAG_SINK_SHUTDOWN_UNLOCK(tsink) \
2415 + GST_TAG_SINK_STREAMS_UNLOCK (tsink); \
2417 +typedef struct _GstTagSink GstTagSink;
2418 +typedef struct _GstTagSinkClass GstTagSinkClass;
2423 + GList *tags; /* tags received through events, sorted */
2424 + GstBuffer *last_buffer;
2425 + guint num_buffers;
2427 + GstTagList *stream_tags; /* merged (non-global) tags */
2428 + GstClockTime duration; /* duration, or GST_CLOCK_TIME_NONE */
2429 + gboolean done; /* got all the info we need */
2430 +} GstTagSinkStream;
2434 + GstBaseSink basesink;
2437 + gint padcount; /* ATOMIC */
2439 + /* if we are shutting down or not */
2440 + gint shutdown; /* ATOMIC */
2442 + gboolean async_pending; /* STREAMS_LOCK */
2444 + /* number of streams for which we don't have all the info we want yet */
2445 + guint num_streams_pending; /* STREAMS_LOCK */
2447 + GStaticRecMutex streams_lock;
2448 + GArray *streams; /* STREAMS_LOCK */
2450 + GstTagList *global_tags; /* STREAMS_LOCK */
2453 +struct _GstTagSinkClass
2455 + GstBaseSinkClass basesink_class;
2458 +GType gst_tag_sink_get_type (void);
2460 +GstTagList * gst_tag_sink_get_global_tags (GstTagSink * sink);
2462 +GstTagList * gst_tag_sink_get_stream_tags (GstTagSink * sink, guint num);
2467 +#endif /* __GST_TAG_SINK_H__ */