blob: c0d2ba380ca6bdfc63150fa1448547da3ffc0e6d [file] [log] [blame]
/* GStreamer
* Copyright (C) 2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>
*
* gst-codec-info.c: tool to print automatic codec installation info
* for a given list of plugins
*
* Partially based on gst-inspect from gstreamer.
*
* 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.
*/
#include <gst/gst.h>
#include <string.h>
static const gchar *virtual_packages[] = {
"gstreamer1.0-audiosink",
"gstreamer1.0-audiosource",
"gstreamer1.0-videosink",
"gstreamer1.0-videosource",
"gstreamer1.0-visualization",
NULL
};
static GList *elements = NULL;
static GList *uri_sources = NULL;
static GList *uri_sinks = NULL;
static GList *provides = NULL;
static GstCaps *encoders = NULL, *decoders = NULL;
static void
free_plugin_info (void)
{
g_list_foreach (elements, (GFunc) g_free, NULL);
g_list_foreach (uri_sources, (GFunc) g_free, NULL);
g_list_foreach (uri_sinks, (GFunc) g_free, NULL);
g_list_free (elements);
g_list_free (uri_sources);
g_list_free (uri_sinks);
g_list_free (provides);
gst_caps_unref (encoders);
gst_caps_unref (decoders);
}
static void
print_plugin_info (void)
{
GList *l;
if (elements) {
g_print ("gstreamer:Elements=");
for (l = elements; l; l = l->next) {
if (l->next)
g_print ("%s, ", (gchar *) l->data);
else
g_print ("%s\n", (gchar *) l->data);
}
}
if (provides) {
g_print ("gstreamer:Provides=");
for (l = provides; l; l = l->next) {
if (l->next)
g_print ("%s, ", (gchar *) l->data);
else
g_print ("%s\n", (gchar *) l->data);
}
}
if (uri_sources) {
g_print ("gstreamer:URISources=");
for (l = uri_sources; l; l = l->next) {
if (l->next)
g_print ("%s, ", (gchar *) l->data);
else
g_print ("%s\n", (gchar *) l->data);
}
}
if (uri_sinks) {
g_print ("gstreamer:URISinks=");
for (l = uri_sinks; l; l = l->next) {
if (l->next)
g_print ("%s, ", (gchar *) l->data);
else
g_print ("%s\n", (gchar *) l->data);
}
}
if (!gst_caps_is_empty (encoders)) {
gchar *caps = gst_caps_to_string (encoders);
g_print ("gstreamer:Encoders=%s\n", caps);
g_free (caps);
}
if (!gst_caps_is_empty (decoders)) {
gchar *caps = gst_caps_to_string (decoders);
g_print ("gstreamer:Decoders=%s\n", caps);
g_free (caps);
}
}
static void
remove_duplicates (GList * list, gboolean free)
{
GList *l;
gchar *previous;
if (!list || !list->next)
return;
previous = list->data;
l = list->next;
while (l) {
if (strcmp (l->data, previous) == 0) {
GList *next = l->next;
if (free)
g_free (l->data);
l = g_list_delete_link (l->prev, l);
l = next;
} else {
previous = l->data;
l = l->next;
}
}
}
static void
cleanup_plugin_info (void)
{
if (encoders)
encoders = gst_caps_simplify (encoders);
if (decoders)
decoders = gst_caps_simplify (decoders);
elements = g_list_sort (elements, (GCompareFunc) strcmp);
uri_sources = g_list_sort (uri_sources, (GCompareFunc) strcmp);
uri_sinks = g_list_sort (uri_sinks, (GCompareFunc) strcmp);
provides = g_list_sort (provides, (GCompareFunc) strcmp);
remove_duplicates (elements, TRUE);
remove_duplicates (uri_sources, TRUE);
remove_duplicates (uri_sinks, TRUE);
remove_duplicates (provides, FALSE);
}
static void
collect_uri_protocols (GstElementFactory * factory)
{
const gchar *const *protocols, *const *p;
protocols = gst_element_factory_get_uri_protocols (factory);
if (!protocols)
return;
switch (gst_element_factory_get_uri_type (factory)) {
case GST_URI_SINK:
for (p = protocols; *p; p++)
uri_sinks = g_list_prepend (uri_sinks, g_strdup (*p));
break;
case GST_URI_SRC:
for (p = protocols; *p; p++)
uri_sources = g_list_prepend (uri_sources, g_strdup (*p));
break;
default:
break;
}
}
static void
remove_min_max_fields (GstStructure * s)
{
gint i, n;
gboolean removed_field = FALSE;
do {
n = gst_structure_n_fields (s);
removed_field = FALSE;
for (i = 0; i < n; i++) {
const gchar *field_name = gst_structure_nth_field_name (s, i);
const GValue *field;
field = gst_structure_get_value (s, field_name);
if (GST_VALUE_HOLDS_INT_RANGE (field)) {
gint min, max;
min = gst_value_get_int_range_min (field);
max = gst_value_get_int_range_max (field);
if (min == 0 && max == G_MAXINT) {
gst_structure_remove_field (s, field_name);
removed_field = TRUE;
break;
}
} else if (GST_VALUE_HOLDS_LIST (field)) {
gint n2 = gst_value_list_get_size (field);
if (n2 == 2) {
const GValue *val1 = gst_value_list_get_value (field, 0);
const GValue *val2 = gst_value_list_get_value (field, 1);
if (G_VALUE_TYPE (val1) == G_TYPE_BOOLEAN
&& G_VALUE_TYPE (val2) == G_TYPE_BOOLEAN
&& ((g_value_get_boolean (val1) && !g_value_get_boolean (val2))
|| (!g_value_get_boolean (val1)
&& g_value_get_boolean (val2)))) {
gst_structure_remove_field (s, field_name);
removed_field = TRUE;
break;
}
}
} else if (GST_VALUE_HOLDS_ARRAY (field)) {
gint n2 = gst_value_array_get_size (field);
if (n2 == 2) {
const GValue *val1 = gst_value_array_get_value (field, 0);
const GValue *val2 = gst_value_array_get_value (field, 1);
if (G_VALUE_TYPE (val1) == G_TYPE_BOOLEAN
&& G_VALUE_TYPE (val2) == G_TYPE_BOOLEAN
&& ((g_value_get_boolean (val1) && !g_value_get_boolean (val2))
|| (!g_value_get_boolean (val1)
&& g_value_get_boolean (val2)))) {
gst_structure_remove_field (s, field_name);
removed_field = TRUE;
break;
}
}
}
}
} while (removed_field);
}
static void
collect_codecs (GstElementFactory * factory)
{
GstPadDirection direction;
gboolean encoder;
const gchar *klass;
const GList *static_templates, *l;
GstCaps *caps = NULL;
gint i, n;
klass = gst_element_factory_get_klass (factory);
g_return_if_fail (klass);
if (strstr (klass, "Demuxer") ||
strstr (klass, "Decoder") ||
strstr (klass, "Depay") || strstr (klass, "Parser")) {
/* Ignore decoders with a less than marginal rank as they're
* not autoplugged by playbin/decodebin */
if (gst_plugin_feature_get_rank (GST_PLUGIN_FEATURE (factory)) <
GST_RANK_MARGINAL)
return;
encoder = FALSE;
direction = GST_PAD_SINK;
} else if (strstr (klass, "Muxer") ||
strstr (klass, "Encoder") || strstr (klass, "Pay")) {
encoder = TRUE;
direction = GST_PAD_SRC;
} else if (strcmp (klass, "Sink/Audio") == 0) {
provides = g_list_prepend (provides, (gchar *) virtual_packages[0]);
return;
} else if (strcmp (klass, "Source/Audio") == 0) {
provides = g_list_prepend (provides, (gchar *) virtual_packages[1]);
return;
} else if (strcmp (klass, "Sink/Video") == 0) {
provides = g_list_prepend (provides, (gchar *) virtual_packages[2]);
return;
} else if (strcmp (klass, "Source/Video") == 0) {
provides = g_list_prepend (provides, (gchar *) virtual_packages[3]);
return;
} else if (strcmp (klass, "Visualization") == 0) {
provides = g_list_prepend (provides, (gchar *) virtual_packages[4]);
return;
} else {
return;
}
/* decoder/demuxer sink pads should always be static and there should only
* be one, the same applies to encoders/muxers and source pads */
static_templates = gst_element_factory_get_static_pad_templates (factory);
for (l = static_templates; l; l = l->next) {
GstStaticPadTemplate *tmpl = l->data;
if (tmpl->direction == direction) {
caps = gst_static_pad_template_get_caps (tmpl);
break;
}
}
if (caps == NULL) {
g_printerr ("W: Couldn't find static pad template for '%s'\n",
GST_OBJECT_NAME (factory));
return;
}
caps = gst_caps_make_writable (caps);
n = gst_caps_get_size (caps);
for (i = 0; i < n; i++) {
GstStructure *s = gst_caps_get_structure (caps, i);
/* make caps easier to interpret, remove common fields that are likely
* to be irrelevant for determining the right plugin (ie. mostly fields
* where template caps usually have the standard MIN - MAX range as value) */
gst_structure_remove_field (s, "codec_data");
gst_structure_remove_field (s, "palette_data");
gst_structure_remove_field (s, "pixel-aspect-ratio");
gst_structure_remove_field (s, "framerate");
gst_structure_remove_field (s, "leaf_size");
gst_structure_remove_field (s, "packet_size");
gst_structure_remove_field (s, "block_align");
gst_structure_remove_field (s, "metadata-interval"); /* icy caps */
/* decoders/encoders almost always handle the usual width/height/channel/rate
* range (and if we don't remove this then the app will have a much harder
* time blacklisting formats it has unsuccessfully tried to install before) */
gst_structure_remove_field (s, "width");
gst_structure_remove_field (s, "depth");
gst_structure_remove_field (s, "height");
gst_structure_remove_field (s, "channels");
gst_structure_remove_field (s, "rate");
/* rtp fields */
gst_structure_remove_field (s, "config");
gst_structure_remove_field (s, "clock-rate");
gst_structure_remove_field (s, "clock-base");
gst_structure_remove_field (s, "maxps");
gst_structure_remove_field (s, "seqnum-base");
gst_structure_remove_field (s, "npt-start");
gst_structure_remove_field (s, "npt-stop");
gst_structure_remove_field (s, "play-speed");
gst_structure_remove_field (s, "play-scale");
gst_structure_remove_field (s, "dynamic_range");
remove_min_max_fields (s);
gst_caps_append_structure ((encoder) ? encoders : decoders,
gst_structure_copy (s));
}
gst_caps_unref (caps);
}
static void
collect_plugin_info (GstPlugin * plugin)
{
GList *features, *l;
const gchar *plugin_name;
plugin_name = gst_plugin_get_name (plugin);
features = gst_registry_get_feature_list (gst_registry_get (),
GST_TYPE_ELEMENT_FACTORY);
for (l = features; l; l = l->next) {
GstPluginFeature *feature = GST_PLUGIN_FEATURE (l->data);
GstElementFactory *factory = GST_ELEMENT_FACTORY (feature);
GstPlugin *f_plugin = gst_plugin_feature_get_plugin (feature);
if (!f_plugin)
continue;
if (!g_str_equal (plugin_name, gst_plugin_get_name (f_plugin))) {
gst_object_unref (f_plugin);
continue;
}
gst_object_unref (f_plugin);
elements =
g_list_prepend (elements,
g_strdup (gst_plugin_feature_get_name (feature)));
collect_uri_protocols (factory);
collect_codecs (factory);
}
g_list_foreach (features, (GFunc) gst_object_unref, NULL);
g_list_free (features);
}
int
main (int argc, char **argv)
{
guint major, minor, micro, nano;
gint i;
if (!g_thread_supported ())
g_thread_init (NULL);
gst_init (NULL, NULL);
gst_version (&major, &minor, &micro, &nano);
if (argc == 1)
return 0;
encoders = gst_caps_new_empty ();
decoders = gst_caps_new_empty ();
for (i = 1; i < argc; i++) {
GstPlugin *plugin = NULL;
GError *error = NULL;
if (argv[i] == NULL ||
!g_file_test (argv[i], G_FILE_TEST_EXISTS) ||
!g_str_has_suffix (argv[i], G_MODULE_SUFFIX)) {
g_printerr ("W: '%s' is no valid plugin filename\n", argv[i]);
continue;
}
plugin = gst_plugin_load_file (argv[i], &error);
if (!plugin) {
g_printerr ("W: Could not load '%s': %s\n", argv[i], error->message);
g_error_free (error);
continue;
}
collect_plugin_info (plugin);
}
if (elements)
g_print ("gstreamer:Version=%u.%u\n", major, minor);
cleanup_plugin_info ();
print_plugin_info ();
free_plugin_info ();
return 0;
}