mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-03 15:06:34 +00:00
8d464c8361
When generating the cache we inspect the base class through an instance of one of its subclasses. We don't want potential assignments in subclasses initialization to leak into the base class documentation Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/641>
510 lines
15 KiB
C
510 lines
15 KiB
C
/* GStreamer
|
|
* (c) 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
|
|
*
|
|
* 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., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <gst/gst.h>
|
|
|
|
#include "gstautodetect.h"
|
|
#include "gstautoaudiosink.h"
|
|
#include "gstautoaudiosrc.h"
|
|
#include "gstautovideosink.h"
|
|
#include "gstautovideosrc.h"
|
|
|
|
GST_DEBUG_CATEGORY (autodetect_debug);
|
|
|
|
#define DEFAULT_SYNC TRUE
|
|
|
|
/* Properties */
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_CAPS,
|
|
PROP_SYNC,
|
|
};
|
|
|
|
static GstStateChangeReturn gst_auto_detect_change_state (GstElement * element,
|
|
GstStateChange transition);
|
|
static void gst_auto_detect_constructed (GObject * object);
|
|
static void gst_auto_detect_dispose (GObject * self);
|
|
static void gst_auto_detect_clear_kid (GstAutoDetect * self);
|
|
static void gst_auto_detect_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_auto_detect_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
#define gst_auto_detect_parent_class parent_class
|
|
G_DEFINE_ABSTRACT_TYPE (GstAutoDetect, gst_auto_detect, GST_TYPE_BIN);
|
|
|
|
static void
|
|
gst_auto_detect_class_init (GstAutoDetectClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *eklass;
|
|
|
|
gobject_class = G_OBJECT_CLASS (klass);
|
|
eklass = GST_ELEMENT_CLASS (klass);
|
|
|
|
gobject_class->constructed = gst_auto_detect_constructed;
|
|
gobject_class->dispose = gst_auto_detect_dispose;
|
|
gobject_class->set_property = gst_auto_detect_set_property;
|
|
gobject_class->get_property = gst_auto_detect_get_property;
|
|
|
|
eklass->change_state = GST_DEBUG_FUNCPTR (gst_auto_detect_change_state);
|
|
|
|
/**
|
|
* GstAutoDetect:filter-caps:
|
|
*
|
|
* This property will filter out candidate sinks that can handle the specified
|
|
* caps. By default only elements that support uncompressed data are selected.
|
|
*
|
|
* This property can only be set before the element goes to the READY state.
|
|
*/
|
|
g_object_class_install_property (gobject_class, PROP_CAPS,
|
|
g_param_spec_boxed ("filter-caps", "Filter caps",
|
|
"Filter sink candidates using these caps.", GST_TYPE_CAPS,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
|
|
GST_PARAM_DOC_SHOW_DEFAULT));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_SYNC,
|
|
g_param_spec_boolean ("sync", "Sync",
|
|
"Sync on the clock", DEFAULT_SYNC,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
gst_type_mark_as_plugin_api (GST_TYPE_AUTO_DETECT, 0);
|
|
}
|
|
|
|
static void
|
|
gst_auto_detect_dispose (GObject * object)
|
|
{
|
|
GstAutoDetect *self = GST_AUTO_DETECT (object);
|
|
|
|
gst_auto_detect_clear_kid (self);
|
|
|
|
if (self->filter_caps)
|
|
gst_caps_unref (self->filter_caps);
|
|
self->filter_caps = NULL;
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose ((GObject *) self);
|
|
}
|
|
|
|
static void
|
|
gst_auto_detect_clear_kid (GstAutoDetect * self)
|
|
{
|
|
if (self->kid) {
|
|
gst_element_set_state (self->kid, GST_STATE_NULL);
|
|
gst_bin_remove (GST_BIN (self), self->kid);
|
|
self->kid = NULL;
|
|
}
|
|
}
|
|
|
|
static GstElement *
|
|
gst_auto_detect_create_fake_element_default (GstAutoDetect * self)
|
|
{
|
|
GstElement *fake;
|
|
gchar dummy_factory[10], dummy_name[20];
|
|
|
|
sprintf (dummy_factory, "fake%s", self->type_klass_lc);
|
|
sprintf (dummy_name, "fake-%s-%s", self->media_klass_lc, self->type_klass_lc);
|
|
fake = gst_element_factory_make (dummy_factory, dummy_name);
|
|
g_object_set (fake, "sync", self->sync, NULL);
|
|
|
|
return fake;
|
|
}
|
|
|
|
static GstElement *
|
|
gst_auto_detect_create_fake_element (GstAutoDetect * self)
|
|
{
|
|
GstAutoDetectClass *klass = GST_AUTO_DETECT_GET_CLASS (self);
|
|
GstElement *fake;
|
|
|
|
if (klass->create_fake_element)
|
|
fake = klass->create_fake_element (self);
|
|
else
|
|
fake = gst_auto_detect_create_fake_element_default (self);
|
|
|
|
return fake;
|
|
}
|
|
|
|
static gboolean
|
|
gst_auto_detect_attach_ghost_pad (GstAutoDetect * self)
|
|
{
|
|
GstPad *target = gst_element_get_static_pad (self->kid, self->type_klass_lc);
|
|
gboolean res = gst_ghost_pad_set_target (GST_GHOST_PAD (self->pad), target);
|
|
gst_object_unref (target);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Hack to make initial linking work; ideally, this would work even when
|
|
* no target has been assigned to the ghostpad yet. */
|
|
static void
|
|
gst_auto_detect_reset (GstAutoDetect * self)
|
|
{
|
|
gst_auto_detect_clear_kid (self);
|
|
|
|
/* placeholder element */
|
|
self->kid = gst_auto_detect_create_fake_element (self);
|
|
gst_bin_add (GST_BIN (self), self->kid);
|
|
|
|
gst_auto_detect_attach_ghost_pad (self);
|
|
}
|
|
|
|
static GstStaticCaps raw_audio_caps = GST_STATIC_CAPS ("audio/x-raw");
|
|
static GstStaticCaps raw_video_caps = GST_STATIC_CAPS ("video/x-raw");
|
|
|
|
static void
|
|
gst_auto_detect_init (GstAutoDetect * self)
|
|
{
|
|
self->sync = DEFAULT_SYNC;
|
|
}
|
|
|
|
static void
|
|
gst_auto_detect_constructed (GObject * object)
|
|
{
|
|
GstAutoDetect *self = GST_AUTO_DETECT (object);
|
|
gboolean is_audio;
|
|
|
|
if (G_OBJECT_CLASS (parent_class)->constructed)
|
|
G_OBJECT_CLASS (parent_class)->constructed (object);
|
|
|
|
is_audio = !g_strcmp0 (self->media_klass, "Audio");
|
|
self->type_klass = (self->flag == GST_ELEMENT_FLAG_SINK) ? "Sink" : "Source";
|
|
self->type_klass_lc = (self->flag == GST_ELEMENT_FLAG_SINK) ? "sink" : "src";
|
|
self->media_klass_lc = is_audio ? "audio" : "video";
|
|
/* set the default raw caps */
|
|
self->filter_caps = gst_static_caps_get (is_audio ? &raw_audio_caps :
|
|
&raw_video_caps);
|
|
|
|
self->pad = gst_ghost_pad_new_no_target (self->type_klass_lc,
|
|
(self->flag == GST_ELEMENT_FLAG_SINK) ? GST_PAD_SINK : GST_PAD_SRC);
|
|
gst_element_add_pad (GST_ELEMENT (self), self->pad);
|
|
|
|
gst_auto_detect_reset (self);
|
|
|
|
/* mark element type */
|
|
GST_OBJECT_FLAG_SET (self, self->flag);
|
|
gst_bin_set_suppressed_flags (GST_BIN (self),
|
|
GST_ELEMENT_FLAG_SOURCE | GST_ELEMENT_FLAG_SINK);
|
|
}
|
|
|
|
static gboolean
|
|
gst_auto_detect_factory_filter (GstPluginFeature * feature, gpointer data)
|
|
{
|
|
GstAutoDetect *self = (GstAutoDetect *) data;
|
|
guint rank;
|
|
const gchar *klass;
|
|
|
|
/* we only care about element factories */
|
|
if (!GST_IS_ELEMENT_FACTORY (feature))
|
|
return FALSE;
|
|
|
|
/* audio sinks */
|
|
klass = gst_element_factory_get_metadata (GST_ELEMENT_FACTORY (feature),
|
|
GST_ELEMENT_METADATA_KLASS);
|
|
if (!(strstr (klass, self->type_klass) && strstr (klass, self->media_klass)))
|
|
return FALSE;
|
|
|
|
/* only select elements with autoplugging rank */
|
|
rank = gst_plugin_feature_get_rank (feature);
|
|
if (rank < GST_RANK_MARGINAL)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstElement *
|
|
create_element_with_pretty_name (GstAutoDetect * self,
|
|
GstElementFactory * factory)
|
|
{
|
|
GstElement *element;
|
|
gchar *name, *marker;
|
|
|
|
marker = g_strdup (GST_OBJECT_NAME (factory));
|
|
if (g_str_has_suffix (marker, self->type_klass_lc))
|
|
marker[strlen (marker) - 4] = '\0';
|
|
if (g_str_has_prefix (marker, "gst"))
|
|
memmove (marker, marker + 3, strlen (marker + 3) + 1);
|
|
name = g_strdup_printf ("%s-actual-%s-%s", GST_OBJECT_NAME (self),
|
|
self->type_klass_lc, marker);
|
|
g_free (marker);
|
|
|
|
element = gst_element_factory_create (factory, name);
|
|
g_free (name);
|
|
|
|
return element;
|
|
}
|
|
|
|
static GstElement *
|
|
gst_auto_detect_find_best (GstAutoDetect * self)
|
|
{
|
|
GList *list, *item;
|
|
GstElement *choice = NULL;
|
|
GstMessage *message = NULL;
|
|
GSList *errors = NULL;
|
|
GstBus *bus = gst_bus_new ();
|
|
GstPad *el_pad = NULL;
|
|
GstCaps *el_caps = NULL;
|
|
gboolean no_match = TRUE;
|
|
|
|
/* We don't treat sound server sinks special. Our policy is that sound
|
|
* server sinks that have a rank must not auto-spawn a daemon under any
|
|
* circumstances, so there's nothing for us to worry about here */
|
|
list = gst_registry_feature_filter (gst_registry_get (),
|
|
(GstPluginFeatureFilter) gst_auto_detect_factory_filter, FALSE, self);
|
|
list =
|
|
g_list_sort (list, (GCompareFunc) gst_plugin_feature_rank_compare_func);
|
|
|
|
GST_LOG_OBJECT (self, "Trying to find usable %s elements ...",
|
|
self->media_klass_lc);
|
|
|
|
for (item = list; item != NULL; item = item->next) {
|
|
GstElementFactory *f = GST_ELEMENT_FACTORY (item->data);
|
|
GstElement *el;
|
|
|
|
if ((el = create_element_with_pretty_name (self, f))) {
|
|
GstStateChangeReturn ret;
|
|
|
|
GST_DEBUG_OBJECT (self, "Testing %s", GST_OBJECT_NAME (f));
|
|
|
|
/* If autodetect has been provided with filter caps,
|
|
* accept only elements that match with the filter caps */
|
|
if (self->filter_caps) {
|
|
el_pad = gst_element_get_static_pad (el, self->type_klass_lc);
|
|
el_caps = gst_pad_query_caps (el_pad, NULL);
|
|
gst_object_unref (el_pad);
|
|
GST_DEBUG_OBJECT (self,
|
|
"Checking caps: %" GST_PTR_FORMAT " vs. %" GST_PTR_FORMAT,
|
|
self->filter_caps, el_caps);
|
|
no_match = !gst_caps_can_intersect (self->filter_caps, el_caps);
|
|
gst_caps_unref (el_caps);
|
|
|
|
if (no_match) {
|
|
GST_DEBUG_OBJECT (self, "Incompatible caps");
|
|
gst_object_unref (el);
|
|
continue;
|
|
} else {
|
|
GST_DEBUG_OBJECT (self, "Found compatible caps");
|
|
}
|
|
}
|
|
|
|
gst_element_set_bus (el, bus);
|
|
ret = gst_element_set_state (el, GST_STATE_READY);
|
|
if (ret == GST_STATE_CHANGE_SUCCESS) {
|
|
GST_DEBUG_OBJECT (self, "This worked!");
|
|
gst_element_set_state (el, GST_STATE_NULL);
|
|
choice = el;
|
|
break;
|
|
}
|
|
|
|
/* collect all error messages */
|
|
while ((message = gst_bus_pop_filtered (bus, GST_MESSAGE_ERROR))) {
|
|
GST_DEBUG_OBJECT (self, "error message %" GST_PTR_FORMAT, message);
|
|
errors = g_slist_append (errors, message);
|
|
}
|
|
|
|
gst_element_set_state (el, GST_STATE_NULL);
|
|
gst_object_unref (el);
|
|
}
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "done trying");
|
|
if (!choice) {
|
|
/* We post a warning and plug a fake-element. This is convenient for running
|
|
* tests without requiring hardware src/sinks. */
|
|
if (errors) {
|
|
GError *err = NULL;
|
|
gchar *dbg = NULL;
|
|
|
|
/* FIXME: we forward the first message for now; but later on it might make
|
|
* sense to forward all so that apps can actually analyse them. */
|
|
gst_message_parse_error (GST_MESSAGE (errors->data), &err, &dbg);
|
|
gst_element_post_message (GST_ELEMENT_CAST (self),
|
|
gst_message_new_warning (GST_OBJECT_CAST (self), err, dbg));
|
|
g_error_free (err);
|
|
g_free (dbg);
|
|
} else {
|
|
/* send warning message to application and use a fakesrc */
|
|
GST_ELEMENT_WARNING (self, RESOURCE, NOT_FOUND, (NULL),
|
|
("Failed to find a usable %s %s", self->media_klass_lc,
|
|
self->type_klass_lc));
|
|
}
|
|
choice = gst_auto_detect_create_fake_element (self);
|
|
gst_element_set_state (choice, GST_STATE_READY);
|
|
}
|
|
gst_object_unref (bus);
|
|
gst_plugin_feature_list_free (list);
|
|
g_slist_foreach (errors, (GFunc) gst_mini_object_unref, NULL);
|
|
g_slist_free (errors);
|
|
|
|
return choice;
|
|
}
|
|
|
|
static gboolean
|
|
gst_auto_detect_detect (GstAutoDetect * self)
|
|
{
|
|
GstElement *kid;
|
|
GstAutoDetectClass *klass = GST_AUTO_DETECT_GET_CLASS (self);
|
|
|
|
gst_auto_detect_clear_kid (self);
|
|
|
|
/* find element */
|
|
GST_DEBUG_OBJECT (self, "Creating new kid");
|
|
if (!(kid = gst_auto_detect_find_best (self)))
|
|
goto no_sink;
|
|
|
|
self->has_sync =
|
|
g_object_class_find_property (G_OBJECT_GET_CLASS (kid), "sync") != NULL;
|
|
if (self->has_sync)
|
|
g_object_set (G_OBJECT (kid), "sync", self->sync, NULL);
|
|
if (klass->configure) {
|
|
klass->configure (self, kid);
|
|
}
|
|
|
|
self->kid = kid;
|
|
|
|
gst_bin_add (GST_BIN (self), kid);
|
|
|
|
/* Ensure the child is brought up to the right state to match the parent. */
|
|
if (GST_STATE (self->kid) < GST_STATE (self))
|
|
gst_element_set_state (self->kid, GST_STATE (self));
|
|
|
|
/* attach ghost pad */
|
|
GST_DEBUG_OBJECT (self, "Re-assigning ghostpad");
|
|
if (!gst_auto_detect_attach_ghost_pad (self))
|
|
goto target_failed;
|
|
|
|
GST_DEBUG_OBJECT (self, "done changing auto %s %s", self->media_klass_lc,
|
|
self->type_klass_lc);
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
no_sink:
|
|
{
|
|
GST_ELEMENT_ERROR (self, LIBRARY, INIT, (NULL),
|
|
("Failed to find a supported audio sink"));
|
|
return FALSE;
|
|
}
|
|
target_failed:
|
|
{
|
|
GST_ELEMENT_ERROR (self, LIBRARY, INIT, (NULL),
|
|
("Failed to set target pad"));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_auto_detect_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
|
|
GstAutoDetect *sink = GST_AUTO_DETECT (element);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_NULL_TO_READY:
|
|
if (!gst_auto_detect_detect (sink))
|
|
return GST_STATE_CHANGE_FAILURE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
if (ret == GST_STATE_CHANGE_FAILURE)
|
|
return ret;
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_READY_TO_NULL:
|
|
gst_auto_detect_reset (sink);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
gst_auto_detect_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstAutoDetect *self = GST_AUTO_DETECT (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_CAPS:
|
|
if (self->filter_caps)
|
|
gst_caps_unref (self->filter_caps);
|
|
self->filter_caps = gst_caps_copy (gst_value_get_caps (value));
|
|
break;
|
|
case PROP_SYNC:
|
|
self->sync = g_value_get_boolean (value);
|
|
if (self->kid && self->has_sync)
|
|
g_object_set_property (G_OBJECT (self->kid), pspec->name, value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_auto_detect_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstAutoDetect *self = GST_AUTO_DETECT (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_CAPS:
|
|
gst_value_set_caps (value, self->filter_caps);
|
|
break;
|
|
case PROP_SYNC:
|
|
g_value_set_boolean (value, self->sync);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
plugin_init (GstPlugin * plugin)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (autodetect_debug, "autodetect", 0,
|
|
"Autodetection audio/video output wrapper elements");
|
|
|
|
return gst_element_register (plugin, "autovideosink",
|
|
GST_RANK_NONE, GST_TYPE_AUTO_VIDEO_SINK) &&
|
|
gst_element_register (plugin, "autovideosrc",
|
|
GST_RANK_NONE, GST_TYPE_AUTO_VIDEO_SRC) &&
|
|
gst_element_register (plugin, "autoaudiosink",
|
|
GST_RANK_NONE, GST_TYPE_AUTO_AUDIO_SINK) &&
|
|
gst_element_register (plugin, "autoaudiosrc",
|
|
GST_RANK_NONE, GST_TYPE_AUTO_AUDIO_SRC);
|
|
}
|
|
|
|
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
|
GST_VERSION_MINOR,
|
|
autodetect,
|
|
"Plugin contains auto-detection plugins for video/audio in- and outputs",
|
|
plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|