gstreamer/plugins/elements/gsttypefindelement.c
Wim Taymans db1318ab4a pad: change the semantics of get/pull_range a little
Make it so that one can specify a buffer for get/pull_range where the downstream
element should write into. When passing NULL, upstream should allocate a buffer,
like in 0.10.
We also need to change the probes a little because before the pull probe, there
could already be a buffer passed. This then allows us to use the same PROBE
macro for before and after pulling.
While we're at the probes, make the query probe more powerful by handling the
GST_PAD_PROBE_DROP return value. Returning _DROP from a query probe will now
return TRUE upstream and will not forward the probe to the peer or handler.
Also handle _DROP for get/pull_range properly by not dispatching to the
peer/handler or by generating EOS when the probe returns DROP and no buffer.
Make filesrc handle the non-NULL buffer passed in the get_range function and
skip the allocation in that case, writing directly into the downstream provided
buffer.
Update tests because now we need to make sure to not pass a random value in the
buffer pointer to get/pull_range
2012-03-16 21:37:10 +01:00

1251 lines
37 KiB
C

/* GStreamer
* Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de>
*
* gsttypefindelement.c: element that detects type of stream
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:element-typefind
*
* Determines the media-type of a stream. It applies typefind functions in the
* order of their rank. One the type has been deteted it sets its src pad caps
* to the found media type.
*
* Whenever a type is found the #GstTypeFindElement::have-type signal is
* emitted, either from the streaming thread or the application thread
* (the latter may happen when typefinding is done pull-based from the
* state change function).
*
* Plugins can register custom typefinders by using #GstTypeFindFactory.
*/
/* FIXME: need a better solution for non-seekable streams */
/* way of operation:
* 1) get a list of all typefind functions sorted best to worst
* 2) if all elements have been called with all requested data goto 8
* 3) call all functions once with all available data
* 4) if a function returns a value >= PROP_MAXIMUM goto 8
* 5) all functions with a result > PROP_MINIMUM or functions that did not get
* all requested data (where peek returned NULL) stay in list
* 6) seek to requested offset of best function that still has open data
* requests
* 7) goto 2
* 8) take best available result and use its caps
*
* The element has two scheduling modes:
*
* 1) chain based, it will collect buffers and run the typefind function on
* the buffer until something is found.
* 2) getrange based, it will proxy the getrange function to the sinkpad. It
* is assumed that the peer element is happy with whatever format we
* eventually read.
*
* By default it tries to do pull based typefinding (this avoids joining
* received buffers and holding them back in store.)
*
* When the element has no connected srcpad, and the sinkpad can operate in
* getrange based mode, the element starts its own task to figure out the
* type of the stream.
*
* Most of the actual implementation is in libs/gst/base/gsttypefindhelper.c.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "gst/gst_private.h"
#include "gsttypefindelement.h"
#include "gst/gst-i18n-lib.h"
#include "gst/base/gsttypefindhelper.h"
#include <gst/gsttypefind.h>
#include <gst/gstutils.h>
#include <gst/gsterror.h>
GST_DEBUG_CATEGORY_STATIC (gst_type_find_element_debug);
#define GST_CAT_DEFAULT gst_type_find_element_debug
/* generic templates */
static GstStaticPadTemplate type_find_element_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
static GstStaticPadTemplate type_find_element_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
/* Require at least 2kB of data before we attempt typefinding in chain-mode.
* 128kB is massive overkill for the maximum, but doesn't do any harm */
#define TYPE_FIND_MIN_SIZE (2*1024)
#define TYPE_FIND_MAX_SIZE (128*1024)
/* TypeFind signals and args */
enum
{
HAVE_TYPE,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_CAPS,
PROP_MINIMUM,
PROP_MAXIMUM,
PROP_FORCE_CAPS,
PROP_LAST
};
enum
{
MODE_NORMAL, /* act as identity */
MODE_TYPEFIND, /* do typefinding */
MODE_ERROR /* had fatal error */
};
#define _do_init \
GST_DEBUG_CATEGORY_INIT (gst_type_find_element_debug, "typefind", \
GST_DEBUG_BG_YELLOW | GST_DEBUG_FG_GREEN, "type finding element");
#define gst_type_find_element_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstTypeFindElement, gst_type_find_element,
GST_TYPE_ELEMENT, _do_init);
static void gst_type_find_element_dispose (GObject * object);
static void gst_type_find_element_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_type_find_element_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
#if 0
static const GstEventMask *gst_type_find_element_src_event_mask (GstPad * pad);
#endif
static gboolean gst_type_find_element_src_event (GstPad * pad,
GstObject * parent, GstEvent * event);
static gboolean gst_type_find_handle_src_query (GstPad * pad,
GstObject * parent, GstQuery * query);
static gboolean gst_type_find_element_sink_event (GstPad * pad,
GstObject * parent, GstEvent * event);
static gboolean gst_type_find_element_setcaps (GstTypeFindElement * typefind,
GstCaps * caps);
static GstFlowReturn gst_type_find_element_chain (GstPad * sinkpad,
GstObject * parent, GstBuffer * buffer);
static GstFlowReturn gst_type_find_element_getrange (GstPad * srcpad,
GstObject * parent, guint64 offset, guint length, GstBuffer ** buffer);
static GstStateChangeReturn
gst_type_find_element_change_state (GstElement * element,
GstStateChange transition);
static gboolean gst_type_find_element_activate_sink (GstPad * pad,
GstObject * parent);
static gboolean gst_type_find_element_activate_sink_mode (GstPad * pad,
GstObject * parent, GstPadMode mode, gboolean active);
static gboolean gst_type_find_element_activate_src_mode (GstPad * pad,
GstObject * parent, GstPadMode mode, gboolean active);
static GstFlowReturn
gst_type_find_element_chain_do_typefinding (GstTypeFindElement * typefind,
gboolean check_avail);
static void gst_type_find_element_send_cached_events (GstTypeFindElement *
typefind);
static void gst_type_find_element_loop (GstPad * pad);
static guint gst_type_find_element_signals[LAST_SIGNAL] = { 0 };
static void
gst_type_find_element_have_type (GstTypeFindElement * typefind,
guint probability, GstCaps * caps)
{
g_assert (caps != NULL);
GST_INFO_OBJECT (typefind, "found caps %" GST_PTR_FORMAT ", probability=%u",
caps, probability);
GST_OBJECT_LOCK (typefind);
if (typefind->caps)
gst_caps_unref (typefind->caps);
typefind->caps = gst_caps_ref (caps);
GST_OBJECT_UNLOCK (typefind);
gst_pad_push_event (typefind->src, gst_event_new_caps (caps));
}
static void
gst_type_find_element_class_init (GstTypeFindElementClass * typefind_class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (typefind_class);
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (typefind_class);
gobject_class->set_property = gst_type_find_element_set_property;
gobject_class->get_property = gst_type_find_element_get_property;
gobject_class->dispose = gst_type_find_element_dispose;
g_object_class_install_property (gobject_class, PROP_CAPS,
g_param_spec_boxed ("caps", _("caps"),
_("detected capabilities in stream"), GST_TYPE_CAPS,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_MINIMUM,
g_param_spec_uint ("minimum", _("minimum"),
"minimum probability required to accept caps", GST_TYPE_FIND_MINIMUM,
GST_TYPE_FIND_MAXIMUM, GST_TYPE_FIND_MINIMUM,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_MAXIMUM,
g_param_spec_uint ("maximum", _("maximum"),
"probability to stop typefinding (deprecated; non-functional)",
GST_TYPE_FIND_MINIMUM, GST_TYPE_FIND_MAXIMUM, GST_TYPE_FIND_MAXIMUM,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_FORCE_CAPS,
g_param_spec_boxed ("force-caps", _("force caps"),
_("force caps without doing a typefind"), GST_TYPE_CAPS,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstTypeFindElement::have-type:
* @typefind: the typefind instance
* @probability: the probability of the type found
* @caps: the caps of the type found
*
* This signal gets emitted when the type and its probability has
* been found.
*/
gst_type_find_element_signals[HAVE_TYPE] = g_signal_new ("have-type",
G_TYPE_FROM_CLASS (typefind_class), G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GstTypeFindElementClass, have_type), NULL, NULL,
g_cclosure_marshal_generic, G_TYPE_NONE, 2,
G_TYPE_UINT, GST_TYPE_CAPS | G_SIGNAL_TYPE_STATIC_SCOPE);
typefind_class->have_type =
GST_DEBUG_FUNCPTR (gst_type_find_element_have_type);
gst_element_class_set_details_simple (gstelement_class,
"TypeFind",
"Generic",
"Finds the media type of a stream",
"Benjamin Otte <in7y118@public.uni-hamburg.de>");
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&type_find_element_src_template));
gst_element_class_add_pad_template (gstelement_class,
gst_static_pad_template_get (&type_find_element_sink_template));
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_type_find_element_change_state);
}
static void
gst_type_find_element_init (GstTypeFindElement * typefind)
{
/* sinkpad */
typefind->sink =
gst_pad_new_from_static_template (&type_find_element_sink_template,
"sink");
gst_pad_set_activate_function (typefind->sink,
GST_DEBUG_FUNCPTR (gst_type_find_element_activate_sink));
gst_pad_set_activatemode_function (typefind->sink,
GST_DEBUG_FUNCPTR (gst_type_find_element_activate_sink_mode));
gst_pad_set_chain_function (typefind->sink,
GST_DEBUG_FUNCPTR (gst_type_find_element_chain));
gst_pad_set_event_function (typefind->sink,
GST_DEBUG_FUNCPTR (gst_type_find_element_sink_event));
GST_PAD_SET_PROXY_ALLOCATION (typefind->sink);
gst_element_add_pad (GST_ELEMENT (typefind), typefind->sink);
/* srcpad */
typefind->src =
gst_pad_new_from_static_template (&type_find_element_src_template, "src");
gst_pad_set_activatemode_function (typefind->src,
GST_DEBUG_FUNCPTR (gst_type_find_element_activate_src_mode));
gst_pad_set_getrange_function (typefind->src,
GST_DEBUG_FUNCPTR (gst_type_find_element_getrange));
gst_pad_set_event_function (typefind->src,
GST_DEBUG_FUNCPTR (gst_type_find_element_src_event));
gst_pad_set_query_function (typefind->src,
GST_DEBUG_FUNCPTR (gst_type_find_handle_src_query));
gst_pad_use_fixed_caps (typefind->src);
gst_element_add_pad (GST_ELEMENT (typefind), typefind->src);
typefind->mode = MODE_TYPEFIND;
typefind->caps = NULL;
typefind->min_probability = 1;
typefind->max_probability = GST_TYPE_FIND_MAXIMUM;
typefind->adapter = gst_adapter_new ();
}
static void
gst_type_find_element_dispose (GObject * object)
{
GstTypeFindElement *typefind = GST_TYPE_FIND_ELEMENT (object);
if (typefind->adapter) {
g_object_unref (typefind->adapter);
typefind->adapter = NULL;
}
if (typefind->force_caps) {
gst_caps_unref (typefind->force_caps);
typefind->force_caps = NULL;
}
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
gst_type_find_element_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstTypeFindElement *typefind;
typefind = GST_TYPE_FIND_ELEMENT (object);
switch (prop_id) {
case PROP_MINIMUM:
typefind->min_probability = g_value_get_uint (value);
break;
case PROP_MAXIMUM:
typefind->max_probability = g_value_get_uint (value);
break;
case PROP_FORCE_CAPS:
GST_OBJECT_LOCK (typefind);
if (typefind->force_caps)
gst_caps_unref (typefind->force_caps);
typefind->force_caps = g_value_dup_boxed (value);
GST_OBJECT_UNLOCK (typefind);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_type_find_element_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstTypeFindElement *typefind;
typefind = GST_TYPE_FIND_ELEMENT (object);
switch (prop_id) {
case PROP_CAPS:
GST_OBJECT_LOCK (typefind);
g_value_set_boxed (value, typefind->caps);
GST_OBJECT_UNLOCK (typefind);
break;
case PROP_MINIMUM:
g_value_set_uint (value, typefind->min_probability);
break;
case PROP_MAXIMUM:
g_value_set_uint (value, typefind->max_probability);
break;
case PROP_FORCE_CAPS:
GST_OBJECT_LOCK (typefind);
g_value_set_boxed (value, typefind->force_caps);
GST_OBJECT_UNLOCK (typefind);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
gst_type_find_handle_src_query (GstPad * pad, GstObject * parent,
GstQuery * query)
{
GstTypeFindElement *typefind;
gboolean res = FALSE;
typefind = GST_TYPE_FIND_ELEMENT (parent);
GST_DEBUG_OBJECT (typefind, "Handling src query %s",
GST_QUERY_TYPE_NAME (query));
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_SCHEDULING:
/* FIXME, filter out the scheduling modes that we understand */
res = gst_pad_peer_query (typefind->sink, query);
break;
case GST_QUERY_CAPS:
{
GST_DEBUG_OBJECT (typefind,
"Got caps query, our caps are %" GST_PTR_FORMAT, typefind->caps);
/* We can hijack caps query if we typefind already */
if (typefind->caps) {
gst_query_set_caps_result (query, typefind->caps);
res = TRUE;
} else {
res = gst_pad_peer_query (typefind->sink, query);
}
break;
}
case GST_QUERY_POSITION:
{
gint64 peer_pos;
GstFormat format;
if (!(res = gst_pad_peer_query (typefind->sink, query)))
goto out;
gst_query_parse_position (query, &format, &peer_pos);
GST_OBJECT_LOCK (typefind);
/* FIXME: this code assumes that there's no discont in the queue */
switch (format) {
case GST_FORMAT_BYTES:
peer_pos -= gst_adapter_available (typefind->adapter);
break;
default:
/* FIXME */
break;
}
GST_OBJECT_UNLOCK (typefind);
gst_query_set_position (query, format, peer_pos);
break;
}
default:
res = gst_pad_query_default (pad, parent, query);
break;
}
out:
return res;
}
static gboolean
gst_type_find_element_seek (GstTypeFindElement * typefind, GstEvent * event)
{
GstSeekFlags flags;
GstSeekType cur_type, stop_type;
GstFormat format;
gboolean flush;
gdouble rate;
gint64 cur, stop;
GstSegment seeksegment = { 0, };
gst_event_parse_seek (event, &rate, &format, &flags, &cur_type, &cur,
&stop_type, &stop);
/* we can only seek on bytes */
if (format != GST_FORMAT_BYTES) {
GST_DEBUG_OBJECT (typefind, "Can only seek on BYTES");
return FALSE;
}
/* copy segment, we need this because we still need the old
* segment when we close the current segment. */
memcpy (&seeksegment, &typefind->segment, sizeof (GstSegment));
GST_DEBUG_OBJECT (typefind, "configuring seek");
gst_segment_do_seek (&seeksegment, rate, format, flags,
cur_type, cur, stop_type, stop, NULL);
flush = ! !(flags & GST_SEEK_FLAG_FLUSH);
GST_DEBUG_OBJECT (typefind, "New segment %" GST_SEGMENT_FORMAT, &seeksegment);
if (flush) {
GST_DEBUG_OBJECT (typefind, "Starting flush");
gst_pad_push_event (typefind->sink, gst_event_new_flush_start ());
gst_pad_push_event (typefind->src, gst_event_new_flush_start ());
} else {
GST_DEBUG_OBJECT (typefind, "Non-flushing seek, pausing task");
gst_pad_pause_task (typefind->sink);
}
/* now grab the stream lock so that streaming cannot continue, for
* non flushing seeks when the element is in PAUSED this could block
* forever. */
GST_DEBUG_OBJECT (typefind, "Waiting for streaming to stop");
GST_PAD_STREAM_LOCK (typefind->sink);
if (flush) {
GST_DEBUG_OBJECT (typefind, "Stopping flush");
gst_pad_push_event (typefind->sink, gst_event_new_flush_stop (TRUE));
gst_pad_push_event (typefind->src, gst_event_new_flush_stop (TRUE));
}
/* now update the real segment info */
GST_DEBUG_OBJECT (typefind, "Committing new seek segment");
memcpy (&typefind->segment, &seeksegment, sizeof (GstSegment));
typefind->offset = typefind->segment.start;
/* notify start of new segment */
if (typefind->segment.flags & GST_SEEK_FLAG_SEGMENT) {
GstMessage *msg;
msg = gst_message_new_segment_start (GST_OBJECT (typefind),
GST_FORMAT_BYTES, typefind->segment.start);
gst_element_post_message (GST_ELEMENT (typefind), msg);
}
typefind->need_segment = TRUE;
/* restart our task since it might have been stopped when we did the
* flush. */
gst_pad_start_task (typefind->sink,
(GstTaskFunction) gst_type_find_element_loop, typefind->sink);
/* streaming can continue now */
GST_PAD_STREAM_UNLOCK (typefind->sink);
return TRUE;
}
static gboolean
gst_type_find_element_src_event (GstPad * pad, GstObject * parent,
GstEvent * event)
{
GstTypeFindElement *typefind = GST_TYPE_FIND_ELEMENT (parent);
if (typefind->mode != MODE_NORMAL) {
/* need to do more? */
gst_mini_object_unref (GST_MINI_OBJECT_CAST (event));
return FALSE;
}
/* Only handle seeks here if driving the pipeline */
if (typefind->segment.format != GST_FORMAT_UNDEFINED &&
GST_EVENT_TYPE (event) == GST_EVENT_SEEK) {
return gst_type_find_element_seek (typefind, event);
} else {
return gst_pad_push_event (typefind->sink, event);
}
}
static void
start_typefinding (GstTypeFindElement * typefind)
{
GST_DEBUG_OBJECT (typefind, "starting typefinding");
GST_OBJECT_LOCK (typefind);
if (typefind->caps)
gst_caps_replace (&typefind->caps, NULL);
GST_OBJECT_UNLOCK (typefind);
typefind->mode = MODE_TYPEFIND;
}
static void
stop_typefinding (GstTypeFindElement * typefind)
{
GstState state;
gboolean push_cached_buffers;
gsize avail;
GstBuffer *buffer;
gst_element_get_state (GST_ELEMENT (typefind), &state, NULL, 0);
push_cached_buffers = (state >= GST_STATE_PAUSED);
GST_DEBUG_OBJECT (typefind, "stopping typefinding%s",
push_cached_buffers ? " and pushing cached buffers" : "");
GST_OBJECT_LOCK (typefind);
avail = gst_adapter_available (typefind->adapter);
if (avail == 0)
goto no_data;
buffer = gst_adapter_take_buffer (typefind->adapter, avail);
GST_OBJECT_UNLOCK (typefind);
if (!push_cached_buffers) {
gst_buffer_unref (buffer);
} else {
GstPad *peer = gst_pad_get_peer (typefind->src);
typefind->mode = MODE_NORMAL;
/* make sure the user gets a meaningful error message in this case,
* which is not a core bug or bug of any kind (as the default error
* message emitted by gstpad.c otherwise would make you think) */
if (peer && GST_PAD_CHAINFUNC (peer) == NULL) {
GST_DEBUG_OBJECT (typefind, "upstream only supports push mode, while "
"downstream element only works in pull mode, erroring out");
GST_ELEMENT_ERROR (typefind, STREAM, FAILED,
("%s cannot work in push mode. The operation is not supported "
"with this source element or protocol.",
G_OBJECT_TYPE_NAME (GST_PAD_PARENT (peer))),
("Downstream pad %s:%s has no chainfunction, and the upstream "
"element does not support pull mode", GST_DEBUG_PAD_NAME (peer)));
typefind->mode = MODE_ERROR; /* make the chain function error out */
gst_buffer_unref (buffer);
} else {
gst_type_find_element_send_cached_events (typefind);
gst_pad_push (typefind->src, buffer);
}
if (peer)
gst_object_unref (peer);
}
return;
/* ERRORS */
no_data:
{
GST_DEBUG_OBJECT (typefind, "we have no data to typefind");
GST_OBJECT_UNLOCK (typefind);
return;
}
}
static gboolean
gst_type_find_element_sink_event (GstPad * pad, GstObject * parent,
GstEvent * event)
{
gboolean res = FALSE;
GstTypeFindElement *typefind = GST_TYPE_FIND_ELEMENT (parent);
GST_DEBUG_OBJECT (typefind, "got %s event in mode %d",
GST_EVENT_TYPE_NAME (event), typefind->mode);
switch (typefind->mode) {
case MODE_TYPEFIND:
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_CAPS:
{
GstCaps *caps;
/* first pass the caps event downstream */
res = gst_pad_push_event (typefind->src, gst_event_ref (event));
/* then parse and push out our data */
gst_event_parse_caps (event, &caps);
res = gst_type_find_element_setcaps (typefind, caps);
gst_event_unref (event);
break;
}
case GST_EVENT_EOS:
{
GST_INFO_OBJECT (typefind, "Got EOS and no type found yet");
gst_type_find_element_chain_do_typefinding (typefind, FALSE);
res = gst_pad_push_event (typefind->src, event);
break;
}
case GST_EVENT_FLUSH_STOP:
GST_OBJECT_LOCK (typefind);
g_list_foreach (typefind->cached_events,
(GFunc) gst_mini_object_unref, NULL);
g_list_free (typefind->cached_events);
typefind->cached_events = NULL;
gst_adapter_clear (typefind->adapter);
GST_OBJECT_UNLOCK (typefind);
/* fall through */
case GST_EVENT_FLUSH_START:
res = gst_pad_push_event (typefind->src, event);
break;
default:
GST_DEBUG_OBJECT (typefind, "Saving %s event to send later",
GST_EVENT_TYPE_NAME (event));
GST_OBJECT_LOCK (typefind);
typefind->cached_events =
g_list_append (typefind->cached_events, event);
GST_OBJECT_UNLOCK (typefind);
res = TRUE;
break;
}
break;
case MODE_NORMAL:
res = gst_pad_push_event (typefind->src, event);
break;
case MODE_ERROR:
break;
default:
g_assert_not_reached ();
}
return res;
}
static void
gst_type_find_element_send_cached_events (GstTypeFindElement * typefind)
{
GList *l, *cached_events;
GST_OBJECT_LOCK (typefind);
cached_events = typefind->cached_events;
typefind->cached_events = NULL;
GST_OBJECT_UNLOCK (typefind);
for (l = cached_events; l != NULL; l = l->next) {
GstEvent *event = GST_EVENT (l->data);
GST_DEBUG_OBJECT (typefind, "sending cached %s event",
GST_EVENT_TYPE_NAME (event));
gst_pad_push_event (typefind->src, event);
}
g_list_free (cached_events);
}
static gboolean
gst_type_find_element_setcaps (GstTypeFindElement * typefind, GstCaps * caps)
{
/* don't operate on ANY caps */
if (gst_caps_is_any (caps))
return TRUE;
g_signal_emit (typefind, gst_type_find_element_signals[HAVE_TYPE], 0,
GST_TYPE_FIND_MAXIMUM, caps);
/* Shortcircuit typefinding if we get caps */
if (typefind->mode == MODE_TYPEFIND) {
GstBuffer *buffer;
gsize avail;
GST_DEBUG_OBJECT (typefind, "Skipping typefinding, using caps from "
"upstream buffer: %" GST_PTR_FORMAT, caps);
typefind->mode = MODE_NORMAL;
gst_type_find_element_send_cached_events (typefind);
GST_OBJECT_LOCK (typefind);
avail = gst_adapter_available (typefind->adapter);
if (avail == 0)
goto no_data;
buffer = gst_adapter_take_buffer (typefind->adapter, avail);
GST_OBJECT_UNLOCK (typefind);
GST_DEBUG_OBJECT (typefind, "Pushing buffer: %" G_GSIZE_FORMAT, avail);
gst_pad_push (typefind->src, buffer);
}
return TRUE;
no_data:
{
GST_DEBUG_OBJECT (typefind, "no data to push");
GST_OBJECT_UNLOCK (typefind);
return TRUE;
}
}
static gchar *
gst_type_find_get_extension (GstTypeFindElement * typefind, GstPad * pad)
{
GstQuery *query;
gchar *uri, *result;
size_t len;
gint find;
query = gst_query_new_uri ();
/* try getting the caps with an uri query and from the extension */
if (!gst_pad_peer_query (pad, query))
goto peer_query_failed;
gst_query_parse_uri (query, &uri);
if (uri == NULL)
goto no_uri;
GST_DEBUG_OBJECT (typefind, "finding extension of %s", uri);
/* find the extension on the uri, this is everything after a '.' */
len = strlen (uri);
find = len - 1;
while (find >= 0) {
if (uri[find] == '.')
break;
find--;
}
if (find < 0)
goto no_extension;
result = g_strdup (&uri[find + 1]);
GST_DEBUG_OBJECT (typefind, "found extension %s", result);
gst_query_unref (query);
g_free (uri);
return result;
/* ERRORS */
peer_query_failed:
{
GST_WARNING_OBJECT (typefind, "failed to query peer uri");
gst_query_unref (query);
return NULL;
}
no_uri:
{
GST_WARNING_OBJECT (typefind, "could not parse the peer uri");
gst_query_unref (query);
return NULL;
}
no_extension:
{
GST_WARNING_OBJECT (typefind, "could not find uri extension in %s", uri);
gst_query_unref (query);
g_free (uri);
return NULL;
}
}
static GstCaps *
gst_type_find_guess_by_extension (GstTypeFindElement * typefind, GstPad * pad,
GstTypeFindProbability * probability)
{
gchar *ext;
GstCaps *caps;
ext = gst_type_find_get_extension (typefind, pad);
if (!ext)
return NULL;
caps = gst_type_find_helper_for_extension (GST_OBJECT_CAST (typefind), ext);
if (caps)
*probability = GST_TYPE_FIND_MAXIMUM;
g_free (ext);
return caps;
}
static GstFlowReturn
gst_type_find_element_chain (GstPad * pad, GstObject * parent,
GstBuffer * buffer)
{
GstTypeFindElement *typefind;
GstFlowReturn res = GST_FLOW_OK;
typefind = GST_TYPE_FIND_ELEMENT (parent);
GST_LOG_OBJECT (typefind, "handling buffer in mode %d", typefind->mode);
switch (typefind->mode) {
case MODE_ERROR:
/* we should already have called GST_ELEMENT_ERROR */
return GST_FLOW_ERROR;
case MODE_NORMAL:
/* don't take object lock as typefind->caps should not change anymore */
return gst_pad_push (typefind->src, buffer);
case MODE_TYPEFIND:
{
GST_OBJECT_LOCK (typefind);
gst_adapter_push (typefind->adapter, buffer);
GST_OBJECT_UNLOCK (typefind);
res = gst_type_find_element_chain_do_typefinding (typefind, TRUE);
if (typefind->mode == MODE_ERROR)
res = GST_FLOW_ERROR;
break;
}
default:
g_assert_not_reached ();
return GST_FLOW_ERROR;
}
return res;
}
static GstFlowReturn
gst_type_find_element_chain_do_typefinding (GstTypeFindElement * typefind,
gboolean check_avail)
{
GstTypeFindProbability probability;
GstCaps *caps;
gsize avail;
const guint8 *data;
gboolean have_min, have_max;
GST_OBJECT_LOCK (typefind);
avail = gst_adapter_available (typefind->adapter);
if (check_avail) {
have_min = avail >= TYPE_FIND_MIN_SIZE;
have_max = avail >= TYPE_FIND_MAX_SIZE;
} else {
have_min = TRUE;
have_max = TRUE;
}
if (!have_min)
goto not_enough_data;
/* map all available data */
data = gst_adapter_map (typefind->adapter, avail);
caps = gst_type_find_helper_for_data (GST_OBJECT (typefind),
data, avail, &probability);
gst_adapter_unmap (typefind->adapter);
if (caps == NULL && have_max)
goto no_type_found;
else if (caps == NULL)
goto wait_for_data;
/* found a type */
if (probability < typefind->min_probability)
goto low_probability;
GST_OBJECT_UNLOCK (typefind);
/* probability is good enough too, so let's make it known ... emiting this
* signal calls our object handler which sets the caps. */
g_signal_emit (typefind, gst_type_find_element_signals[HAVE_TYPE], 0,
probability, caps);
/* .. and send out the accumulated data */
stop_typefinding (typefind);
gst_caps_unref (caps);
return GST_FLOW_OK;
not_enough_data:
{
GST_DEBUG_OBJECT (typefind, "not enough data for typefinding yet "
"(%" G_GSIZE_FORMAT " bytes)", avail);
GST_OBJECT_UNLOCK (typefind);
return GST_FLOW_OK;
}
no_type_found:
{
GST_OBJECT_UNLOCK (typefind);
GST_ELEMENT_ERROR (typefind, STREAM, TYPE_NOT_FOUND, (NULL), (NULL));
stop_typefinding (typefind);
return GST_FLOW_ERROR;
}
wait_for_data:
{
GST_DEBUG_OBJECT (typefind,
"no caps found with %" G_GSIZE_FORMAT " bytes of data, "
"waiting for more data", avail);
GST_OBJECT_UNLOCK (typefind);
return GST_FLOW_OK;
}
low_probability:
{
GST_DEBUG_OBJECT (typefind, "found caps %" GST_PTR_FORMAT ", but "
"probability is %u which is lower than the required minimum of %u",
caps, probability, typefind->min_probability);
gst_caps_unref (caps);
if (have_max)
goto no_type_found;
GST_OBJECT_UNLOCK (typefind);
GST_DEBUG_OBJECT (typefind, "waiting for more data to try again");
return GST_FLOW_OK;
}
}
static GstFlowReturn
gst_type_find_element_getrange (GstPad * srcpad, GstObject * parent,
guint64 offset, guint length, GstBuffer ** buffer)
{
GstTypeFindElement *typefind;
GstFlowReturn ret;
typefind = GST_TYPE_FIND_ELEMENT (parent);
ret = gst_pad_pull_range (typefind->sink, offset, length, buffer);
return ret;
}
static gboolean
gst_type_find_element_activate_src_mode (GstPad * pad, GstObject * parent,
GstPadMode mode, gboolean active)
{
gboolean res;
GstTypeFindElement *typefind;
typefind = GST_TYPE_FIND_ELEMENT (parent);
switch (mode) {
case GST_PAD_MODE_PULL:
/* make sure our task stops pushing, we can't call _stop here because this
* activation might happen from the streaming thread. */
gst_pad_pause_task (typefind->sink);
res = gst_pad_activate_mode (typefind->sink, mode, active);
if (typefind->caps) {
GstCaps *caps;
GST_OBJECT_LOCK (typefind);
caps = gst_caps_ref (typefind->caps);
GST_OBJECT_UNLOCK (typefind);
gst_pad_push_event (typefind->src, gst_event_new_caps (caps));
gst_caps_unref (caps);
}
break;
default:
res = TRUE;
break;
}
return res;
}
static void
gst_type_find_element_loop (GstPad * pad)
{
GstTypeFindElement *typefind;
GstFlowReturn ret = GST_FLOW_OK;
typefind = GST_TYPE_FIND_ELEMENT (GST_PAD_PARENT (pad));
if (typefind->mode == MODE_TYPEFIND) {
GstPad *peer;
GstCaps *found_caps = NULL;
GstTypeFindProbability probability = GST_TYPE_FIND_NONE;
GST_DEBUG_OBJECT (typefind, "find type in pull mode");
peer = gst_pad_get_peer (pad);
if (peer) {
gint64 size;
gchar *ext;
if (!gst_pad_query_duration (peer, GST_FORMAT_BYTES, &size)) {
GST_WARNING_OBJECT (typefind, "Could not query upstream length!");
gst_object_unref (peer);
ret = GST_FLOW_ERROR;
goto pause;
}
/* the size if 0, we cannot continue */
if (size == 0) {
/* keep message in sync with message in sink event handler */
GST_ELEMENT_ERROR (typefind, STREAM, TYPE_NOT_FOUND,
(_("Stream contains no data.")), ("Can't typefind empty stream"));
gst_object_unref (peer);
ret = GST_FLOW_ERROR;
goto pause;
}
ext = gst_type_find_get_extension (typefind, pad);
found_caps =
gst_type_find_helper_get_range (GST_OBJECT_CAST (peer),
GST_OBJECT_PARENT (peer),
(GstTypeFindHelperGetRangeFunction) (GST_PAD_GETRANGEFUNC (peer)),
(guint64) size, ext, &probability);
g_free (ext);
GST_DEBUG ("Found caps %" GST_PTR_FORMAT, found_caps);
gst_object_unref (peer);
}
if (!found_caps || probability < typefind->min_probability) {
GST_DEBUG ("Trying to guess using extension");
found_caps =
gst_type_find_guess_by_extension (typefind, pad, &probability);
}
if (!found_caps || probability < typefind->min_probability) {
GST_ELEMENT_ERROR (typefind, STREAM, TYPE_NOT_FOUND, (NULL), (NULL));
gst_caps_replace (&found_caps, NULL);
ret = GST_FLOW_ERROR;
goto pause;
}
GST_DEBUG ("Emiting found caps %" GST_PTR_FORMAT, found_caps);
g_signal_emit (typefind, gst_type_find_element_signals[HAVE_TYPE],
0, probability, found_caps);
typefind->mode = MODE_NORMAL;
} else if (typefind->mode == MODE_NORMAL) {
GstBuffer *outbuf = NULL;
if (typefind->need_segment) {
typefind->need_segment = FALSE;
gst_pad_push_event (typefind->src,
gst_event_new_segment (&typefind->segment));
}
/* Pull 4k blocks and send downstream */
ret = gst_pad_pull_range (typefind->sink, typefind->offset, 4096, &outbuf);
if (ret != GST_FLOW_OK)
goto pause;
typefind->offset += 4096;
ret = gst_pad_push (typefind->src, outbuf);
if (ret != GST_FLOW_OK)
goto pause;
} else {
/* Error out */
ret = GST_FLOW_ERROR;
goto pause;
}
return;
pause:
{
const gchar *reason = gst_flow_get_name (ret);
gboolean push_eos = FALSE;
GST_LOG_OBJECT (typefind, "pausing task, reason %s", reason);
gst_pad_pause_task (typefind->sink);
if (ret == GST_FLOW_EOS) {
/* perform EOS logic */
if (typefind->segment.flags & GST_SEEK_FLAG_SEGMENT) {
gint64 stop;
/* for segment playback we need to post when (in stream time)
* we stopped, this is either stop (when set) or the duration. */
if ((stop = typefind->segment.stop) == -1)
stop = typefind->offset;
GST_LOG_OBJECT (typefind, "Sending segment done, at end of segment");
gst_element_post_message (GST_ELEMENT (typefind),
gst_message_new_segment_done (GST_OBJECT (typefind),
GST_FORMAT_BYTES, stop));
} else {
push_eos = TRUE;
}
} else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) {
/* for fatal errors we post an error message */
GST_ELEMENT_ERROR (typefind, STREAM, FAILED, (NULL),
("stream stopped, reason %s", reason));
push_eos = TRUE;
}
if (push_eos) {
/* send EOS, and prevent hanging if no streams yet */
GST_LOG_OBJECT (typefind, "Sending EOS, at end of stream");
gst_pad_push_event (typefind->src, gst_event_new_eos ());
}
return;
}
}
static gboolean
gst_type_find_element_activate_sink_mode (GstPad * pad, GstObject * parent,
GstPadMode mode, gboolean active)
{
gboolean res;
GstTypeFindElement *typefind;
typefind = GST_TYPE_FIND_ELEMENT (parent);
switch (mode) {
case GST_PAD_MODE_PULL:
if (active) {
gst_segment_init (&typefind->segment, GST_FORMAT_BYTES);
typefind->need_segment = TRUE;
typefind->offset = 0;
res = TRUE;
} else {
res = gst_pad_stop_task (pad);
}
break;
case GST_PAD_MODE_PUSH:
if (active)
start_typefinding (typefind);
else
stop_typefinding (typefind);
res = TRUE;
break;
default:
res = FALSE;
break;
}
return res;
}
static gboolean
gst_type_find_element_activate_sink (GstPad * pad, GstObject * parent)
{
GstTypeFindElement *typefind;
GstQuery *query;
gboolean pull_mode;
GstCaps *found_caps = NULL;
GstTypeFindProbability probability = GST_TYPE_FIND_NONE;
typefind = GST_TYPE_FIND_ELEMENT (parent);
/* if we have force caps, use those */
GST_OBJECT_LOCK (typefind);
if (typefind->force_caps) {
found_caps = gst_caps_ref (typefind->force_caps);
probability = GST_TYPE_FIND_MAXIMUM;
GST_OBJECT_UNLOCK (typefind);
GST_DEBUG ("Emiting found caps %" GST_PTR_FORMAT, found_caps);
g_signal_emit (typefind, gst_type_find_element_signals[HAVE_TYPE],
0, probability, found_caps);
typefind->mode = MODE_NORMAL;
goto typefind_push;
}
GST_OBJECT_UNLOCK (typefind);
query = gst_query_new_scheduling ();
if (!gst_pad_peer_query (pad, query)) {
gst_query_unref (query);
goto typefind_push;
}
pull_mode = gst_query_has_scheduling_mode (query, GST_PAD_MODE_PULL);
gst_query_unref (query);
if (!pull_mode)
goto typefind_push;
if (!gst_pad_activate_mode (pad, GST_PAD_MODE_PULL, TRUE))
goto typefind_push;
/* only start our task if we ourselves decide to start in pull mode */
return gst_pad_start_task (pad, (GstTaskFunction) gst_type_find_element_loop,
pad);
typefind_push:
{
return gst_pad_activate_mode (pad, GST_PAD_MODE_PUSH, TRUE);
}
}
static GstStateChangeReturn
gst_type_find_element_change_state (GstElement * element,
GstStateChange transition)
{
GstStateChangeReturn ret;
GstTypeFindElement *typefind;
typefind = GST_TYPE_FIND_ELEMENT (element);
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
case GST_STATE_CHANGE_READY_TO_NULL:
GST_OBJECT_LOCK (typefind);
gst_caps_replace (&typefind->caps, NULL);
g_list_foreach (typefind->cached_events,
(GFunc) gst_mini_object_unref, NULL);
g_list_free (typefind->cached_events);
typefind->cached_events = NULL;
typefind->mode = MODE_TYPEFIND;
GST_OBJECT_UNLOCK (typefind);
break;
default:
break;
}
return ret;
}