| /* 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, µ, &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; |
| } |