/* GStreamer * Copyright (C) 2008 Sebastian Dröge * * 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 #include 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, µ, &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; }