/* GStreamer base utils library missing plugins support * Copyright (C) 2006 Tim-Philipp Müller <tim centricular 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. */ /** * SECTION:gstpbutilsmissingplugins * @title: Missing plugins * @short_description: Create, recognise and parse missing-plugins messages * * Functions to create, recognise and parse missing-plugins messages for * applications and elements. * * Missing-plugin messages are posted on the bus by elements like decodebin * or playbin if they can't find an appropriate source element or decoder * element. The application can use these messages for two things: * * * concise error/problem reporting to the user mentioning what exactly * is missing, see gst_missing_plugin_message_get_description() * * * initiate installation of missing plugins, see * gst_missing_plugin_message_get_installer_detail() and * gst_install_plugins_async() * * Applications may also create missing-plugin messages themselves to install * required elements that are missing, using the install mechanism mentioned * above. * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef HAVE_SYS_TYPES_H # include <sys/types.h> #endif #ifdef HAVE_UNISTD_H # include <unistd.h> /* getpid on UNIX */ #endif #include <glib/gi18n-lib.h> #include "pbutils.h" #include "pbutils-private.h" #include <string.h> #ifdef G_OS_WIN32 #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <processthreadsapi.h> #endif #ifndef GST_DISABLE_GST_DEBUG #define GST_CAT_DEFAULT gst_pb_utils_missing_plugins_ensure_debug_category() static GstDebugCategory * gst_pb_utils_missing_plugins_ensure_debug_category (void) { static gsize cat_gonce = 0; if (g_once_init_enter (&cat_gonce)) { GstDebugCategory *cat = NULL; GST_DEBUG_CATEGORY_INIT (cat, "missing-plugins", 0, "GstPbUtils missing plugins helper"); g_once_init_leave (&cat_gonce, (gsize) cat); } return (GstDebugCategory *) cat_gonce; } #endif /* GST_DISABLE_GST_DEBUG */ /* use glib's abstraction once it's landed * https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2475 */ #ifdef G_OS_WIN32 static inline DWORD _gst_getpid (void) { return GetCurrentProcessId (); } #else static inline pid_t _gst_getpid (void) { return getpid (); } #endif #define GST_DETAIL_STRING_MARKER "gstreamer" typedef enum { GST_MISSING_TYPE_UNKNOWN = 0, GST_MISSING_TYPE_URISOURCE, GST_MISSING_TYPE_URISINK, GST_MISSING_TYPE_ELEMENT, GST_MISSING_TYPE_DECODER, GST_MISSING_TYPE_ENCODER } GstMissingType; static const struct { GstMissingType type; const gchar type_string[12]; } missing_type_mapping[] = { { GST_MISSING_TYPE_URISOURCE, "urisource"}, { GST_MISSING_TYPE_URISINK, "urisink"}, { GST_MISSING_TYPE_ELEMENT, "element"}, { GST_MISSING_TYPE_DECODER, "decoder"}, { GST_MISSING_TYPE_ENCODER, "encoder"} }; static GstMissingType missing_structure_get_type (const GstStructure * s) { const gchar *type; guint i; type = gst_structure_get_string (s, "type"); g_return_val_if_fail (type != NULL, GST_MISSING_TYPE_UNKNOWN); for (i = 0; i < G_N_ELEMENTS (missing_type_mapping); ++i) { if (strcmp (missing_type_mapping[i].type_string, type) == 0) return missing_type_mapping[i].type; } return GST_MISSING_TYPE_UNKNOWN; } GstCaps * copy_and_clean_caps (const GstCaps * caps) { GstStructure *s; GstCaps *ret; ret = gst_caps_copy (caps); /* 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) */ s = gst_caps_get_structure (ret, 0); gst_structure_remove_field (s, "codec_data"); gst_structure_remove_field (s, "streamheader"); 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"); /* parsed, framed, stream-format and alignment are going to be handled by * parsers and not relevant for decoders/encoders usually */ gst_structure_remove_field (s, "parsed"); gst_structure_remove_field (s, "framed"); gst_structure_remove_field (s, "stream-format"); gst_structure_remove_field (s, "alignment"); /* rtp fields */ gst_structure_remove_field (s, "config"); gst_structure_remove_field (s, "clock-rate"); gst_structure_remove_field (s, "timestamp-offset"); gst_structure_remove_field (s, "maxps"); gst_structure_remove_field (s, "seqnum-offset"); 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"); return ret; } /** * gst_missing_uri_source_message_new: * @element: the #GstElement posting the message * @protocol: the URI protocol the missing source needs to implement, * e.g. "http" or "mms" * * Creates a missing-plugin message for @element to notify the application * that a source element for a particular URI protocol is missing. This * function is mainly for use in plugins. * * Returns: (transfer full): a new #GstMessage */ GstMessage * gst_missing_uri_source_message_new (GstElement * element, const gchar * protocol) { GstStructure *s; gchar *description; g_return_val_if_fail (element != NULL, NULL); g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); g_return_val_if_fail (protocol != NULL, NULL); description = gst_pb_utils_get_source_description (protocol); s = gst_structure_new ("missing-plugin", "type", G_TYPE_STRING, "urisource", "detail", G_TYPE_STRING, protocol, "name", G_TYPE_STRING, description, NULL); g_free (description); return gst_message_new_element (GST_OBJECT_CAST (element), s); } /** * gst_missing_uri_sink_message_new: * @element: the #GstElement posting the message * @protocol: the URI protocol the missing sink needs to implement, * e.g. "http" or "smb" * * Creates a missing-plugin message for @element to notify the application * that a sink element for a particular URI protocol is missing. This * function is mainly for use in plugins. * * Returns: (transfer full): a new #GstMessage */ GstMessage * gst_missing_uri_sink_message_new (GstElement * element, const gchar * protocol) { GstStructure *s; gchar *description; g_return_val_if_fail (element != NULL, NULL); g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); g_return_val_if_fail (protocol != NULL, NULL); description = gst_pb_utils_get_sink_description (protocol); s = gst_structure_new ("missing-plugin", "type", G_TYPE_STRING, "urisink", "detail", G_TYPE_STRING, protocol, "name", G_TYPE_STRING, description, NULL); g_free (description); return gst_message_new_element (GST_OBJECT_CAST (element), s); } /** * gst_missing_element_message_new: * @element: the #GstElement posting the message * @factory_name: the name of the missing element (element factory), * e.g. "videoscale" or "cdparanoiasrc" * * Creates a missing-plugin message for @element to notify the application * that a certain required element is missing. This function is mainly for * use in plugins. * * Returns: (transfer full): a new #GstMessage */ GstMessage * gst_missing_element_message_new (GstElement * element, const gchar * factory_name) { GstStructure *s; gchar *description; g_return_val_if_fail (element != NULL, NULL); g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); g_return_val_if_fail (factory_name != NULL, NULL); description = gst_pb_utils_get_element_description (factory_name); s = gst_structure_new ("missing-plugin", "type", G_TYPE_STRING, "element", "detail", G_TYPE_STRING, factory_name, "name", G_TYPE_STRING, description, NULL); g_free (description); return gst_message_new_element (GST_OBJECT_CAST (element), s); } /** * gst_missing_decoder_message_new: * @element: the #GstElement posting the message * @decode_caps: the (fixed) caps for which a decoder element is needed * * Creates a missing-plugin message for @element to notify the application * that a decoder element for a particular set of (fixed) caps is missing. * This function is mainly for use in plugins. * * Returns: (transfer full): a new #GstMessage */ GstMessage * gst_missing_decoder_message_new (GstElement * element, const GstCaps * decode_caps) { GstStructure *s; GstCaps *caps; gchar *description; g_return_val_if_fail (element != NULL, NULL); g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); g_return_val_if_fail (decode_caps != NULL, NULL); g_return_val_if_fail (GST_IS_CAPS (decode_caps), NULL); g_return_val_if_fail (!gst_caps_is_any (decode_caps), NULL); g_return_val_if_fail (!gst_caps_is_empty (decode_caps), NULL); g_return_val_if_fail (gst_caps_is_fixed (decode_caps), NULL); description = gst_pb_utils_get_decoder_description (decode_caps); caps = copy_and_clean_caps (decode_caps); s = gst_structure_new ("missing-plugin", "type", G_TYPE_STRING, "decoder", "detail", GST_TYPE_CAPS, caps, "name", G_TYPE_STRING, description, NULL); gst_caps_unref (caps); g_free (description); return gst_message_new_element (GST_OBJECT_CAST (element), s); } /** * gst_missing_encoder_message_new: * @element: the #GstElement posting the message * @encode_caps: the (fixed) caps for which an encoder element is needed * * Creates a missing-plugin message for @element to notify the application * that an encoder element for a particular set of (fixed) caps is missing. * This function is mainly for use in plugins. * * Returns: (transfer full): a new #GstMessage */ GstMessage * gst_missing_encoder_message_new (GstElement * element, const GstCaps * encode_caps) { GstStructure *s; GstCaps *caps; gchar *description; g_return_val_if_fail (element != NULL, NULL); g_return_val_if_fail (GST_IS_ELEMENT (element), NULL); g_return_val_if_fail (encode_caps != NULL, NULL); g_return_val_if_fail (GST_IS_CAPS (encode_caps), NULL); g_return_val_if_fail (!gst_caps_is_any (encode_caps), NULL); g_return_val_if_fail (!gst_caps_is_empty (encode_caps), NULL); g_return_val_if_fail (gst_caps_is_fixed (encode_caps), NULL); description = gst_pb_utils_get_encoder_description (encode_caps); caps = copy_and_clean_caps (encode_caps); s = gst_structure_new ("missing-plugin", "type", G_TYPE_STRING, "encoder", "detail", GST_TYPE_CAPS, caps, "name", G_TYPE_STRING, description, NULL); gst_caps_unref (caps); g_free (description); return gst_message_new_element (GST_OBJECT_CAST (element), s); } static gboolean missing_structure_get_string_detail (const GstStructure * s, gchar ** p_detail) { const gchar *detail; GType detail_type; *p_detail = NULL; detail_type = gst_structure_get_field_type (s, "detail"); if (!g_type_is_a (detail_type, G_TYPE_STRING)) { GST_WARNING ("expected 'detail' field to be of G_TYPE_STRING"); return FALSE; } detail = gst_structure_get_string (s, "detail"); if (detail == NULL || *detail == '\0') { GST_WARNING ("empty 'detail' field"); return FALSE; } *p_detail = g_strdup (detail); return TRUE; } static gboolean missing_structure_get_caps_detail (const GstStructure * s, GstCaps ** p_caps) { const GstCaps *caps; const GValue *val; GType detail_type; *p_caps = NULL; detail_type = gst_structure_get_field_type (s, "detail"); if (!g_type_is_a (detail_type, GST_TYPE_CAPS)) { GST_WARNING ("expected 'detail' field to be of GST_TYPE_CAPS"); return FALSE; } val = gst_structure_get_value (s, "detail"); caps = gst_value_get_caps (val); if (gst_caps_is_empty (caps) || gst_caps_is_any (caps)) { GST_WARNING ("EMPTY or ANY caps not allowed"); return FALSE; } *p_caps = gst_caps_copy (caps); return TRUE; } /** * gst_missing_plugin_message_get_installer_detail: * @msg: a missing-plugin #GstMessage of type #GST_MESSAGE_ELEMENT * * Returns an opaque string containing all the details about the missing * element to be passed to an external installer called via * gst_install_plugins_async() or gst_install_plugins_sync(). * * This function is mainly for applications that call external plugin * installation mechanisms using one of the two above-mentioned functions. * * Returns: (nullable): a newly-allocated detail string, or NULL on error. Free string * with g_free() when not needed any longer. */ gchar * gst_missing_plugin_message_get_installer_detail (GstMessage * msg) { GstMissingType missing_type; const gchar *progname; const gchar *type; GString *str = NULL; gchar *detail = NULL; gchar *desc; const GstStructure *structure; g_return_val_if_fail (gst_is_missing_plugin_message (msg), NULL); structure = gst_message_get_structure (msg); GST_LOG ("Parsing missing-plugin message: %" GST_PTR_FORMAT, structure); missing_type = missing_structure_get_type (structure); if (missing_type == GST_MISSING_TYPE_UNKNOWN) { GST_WARNING ("couldn't parse 'type' field"); goto error; } type = gst_structure_get_string (structure, "type"); g_assert (type != NULL); /* validity already checked above */ /* FIXME: use gst_installer_detail_new() here too */ str = g_string_new (GST_DETAIL_STRING_MARKER "|"); g_string_append_printf (str, "%s|", GST_API_VERSION); progname = (const gchar *) g_get_prgname (); if (progname) { g_string_append_printf (str, "%s|", progname); } else { g_string_append_printf (str, "pid/%lu|", (gulong) _gst_getpid ()); } desc = gst_missing_plugin_message_get_description (msg); if (desc) { g_strdelimit (desc, "|", '#'); g_string_append_printf (str, "%s|", desc); g_free (desc); } else { g_string_append (str, "|"); } switch (missing_type) { case GST_MISSING_TYPE_URISOURCE: case GST_MISSING_TYPE_URISINK: case GST_MISSING_TYPE_ELEMENT: if (!missing_structure_get_string_detail (structure, &detail)) goto error; break; case GST_MISSING_TYPE_DECODER: case GST_MISSING_TYPE_ENCODER:{ GstCaps *caps = NULL; if (!missing_structure_get_caps_detail (structure, &caps)) goto error; detail = gst_caps_to_string (caps); gst_caps_unref (caps); break; } default: g_return_val_if_reached (NULL); } g_string_append_printf (str, "%s-%s", type, detail); g_free (detail); return g_string_free (str, FALSE); /* ERRORS */ error: { GST_WARNING ("Failed to parse missing-plugin msg: %" GST_PTR_FORMAT, msg); if (str) g_string_free (str, TRUE); return NULL; } } /** * gst_missing_plugin_message_get_description: * @msg: a missing-plugin #GstMessage of type #GST_MESSAGE_ELEMENT * * Returns a localised string describing the missing feature, for use in * error dialogs and the like. Should never return NULL unless @msg is not * a valid missing-plugin message. * * This function is mainly for applications that need a human-readable string * describing a missing plugin, given a previously collected missing-plugin * message * * Returns: a newly-allocated description string. Free * string with g_free() when not needed any longer. */ gchar * gst_missing_plugin_message_get_description (GstMessage * msg) { GstMissingType missing_type; const gchar *desc; gchar *ret = NULL; const GstStructure *structure; g_return_val_if_fail (gst_is_missing_plugin_message (msg), NULL); structure = gst_message_get_structure (msg); GST_LOG ("Parsing missing-plugin message: %" GST_PTR_FORMAT, structure); desc = gst_structure_get_string (structure, "name"); if (desc != NULL && *desc != '\0') { ret = g_strdup (desc); goto done; } /* fallback #1 */ missing_type = missing_structure_get_type (structure); switch (missing_type) { case GST_MISSING_TYPE_URISOURCE: case GST_MISSING_TYPE_URISINK: case GST_MISSING_TYPE_ELEMENT:{ gchar *detail = NULL; if (missing_structure_get_string_detail (structure, &detail)) { if (missing_type == GST_MISSING_TYPE_URISOURCE) ret = gst_pb_utils_get_source_description (detail); else if (missing_type == GST_MISSING_TYPE_URISINK) ret = gst_pb_utils_get_sink_description (detail); else ret = gst_pb_utils_get_element_description (detail); g_free (detail); } break; } case GST_MISSING_TYPE_DECODER: case GST_MISSING_TYPE_ENCODER:{ GstCaps *caps = NULL; if (missing_structure_get_caps_detail (structure, &caps)) { if (missing_type == GST_MISSING_TYPE_DECODER) ret = gst_pb_utils_get_decoder_description (caps); else ret = gst_pb_utils_get_encoder_description (caps); gst_caps_unref (caps); } break; } default: break; } if (ret) goto done; /* fallback #2 */ switch (missing_type) { case GST_MISSING_TYPE_URISOURCE: desc = _("Unknown source element"); break; case GST_MISSING_TYPE_URISINK: desc = _("Unknown sink element"); break; case GST_MISSING_TYPE_ELEMENT: desc = _("Unknown element"); break; case GST_MISSING_TYPE_DECODER: desc = _("Unknown decoder element"); break; case GST_MISSING_TYPE_ENCODER: desc = _("Unknown encoder element"); break; default: /* we should really never get here, but we better still return * something if we do */ desc = _("Plugin or element of unknown type"); break; } ret = g_strdup (desc); done: GST_LOG ("returning '%s'", ret); return ret; } /** * gst_is_missing_plugin_message: * @msg: a #GstMessage * * Checks whether @msg is a missing plugins message. * * Returns: %TRUE if @msg is a missing-plugins message, otherwise %FALSE. */ gboolean gst_is_missing_plugin_message (GstMessage * msg) { const GstStructure *structure; g_return_val_if_fail (msg != NULL, FALSE); g_return_val_if_fail (GST_IS_MESSAGE (msg), FALSE); structure = gst_message_get_structure (msg); if (GST_MESSAGE_TYPE (msg) != GST_MESSAGE_ELEMENT || structure == NULL) return FALSE; return gst_structure_has_name (structure, "missing-plugin"); } /* takes ownership of the description */ static gchar * gst_installer_detail_new (gchar * description, const gchar * type, const gchar * detail) { const gchar *progname; GString *s; s = g_string_new (GST_DETAIL_STRING_MARKER "|"); g_string_append_printf (s, "%s|", GST_API_VERSION); progname = (const gchar *) g_get_prgname (); if (progname) { g_string_append_printf (s, "%s|", progname); } else { g_string_append_printf (s, "pid/%lu|", (gulong) _gst_getpid ()); } if (description) { g_strdelimit (description, "|", '#'); g_string_append_printf (s, "%s|", description); g_free (description); } else { g_string_append (s, "|"); } g_string_append_printf (s, "%s-%s", type, detail); return g_string_free (s, FALSE); } /** * gst_missing_uri_source_installer_detail_new: * @protocol: the URI protocol the missing source needs to implement, * e.g. "http" or "mms" * * Returns an opaque string containing all the details about the missing * element to be passed to an external installer called via * gst_install_plugins_async() or gst_install_plugins_sync(). * * This function is mainly for applications that call external plugin * installation mechanisms using one of the two above-mentioned functions in * the case where the application knows exactly what kind of plugin it is * missing. * * Returns: (transfer full): a newly-allocated detail string. Free string * with g_free() when not needed any longer. */ gchar * gst_missing_uri_source_installer_detail_new (const gchar * protocol) { gchar *desc; g_return_val_if_fail (protocol != NULL, NULL); desc = gst_pb_utils_get_source_description (protocol); return gst_installer_detail_new (desc, "urisource", protocol); } /** * gst_missing_uri_sink_installer_detail_new: * @protocol: the URI protocol the missing source needs to implement, * e.g. "http" or "mms" * * Returns an opaque string containing all the details about the missing * element to be passed to an external installer called via * gst_install_plugins_async() or gst_install_plugins_sync(). * * This function is mainly for applications that call external plugin * installation mechanisms using one of the two above-mentioned functions in * the case where the application knows exactly what kind of plugin it is * missing. * * Returns: (transfer full): a newly-allocated detail string. Free string * with g_free() when not needed any longer. */ gchar * gst_missing_uri_sink_installer_detail_new (const gchar * protocol) { gchar *desc; g_return_val_if_fail (protocol != NULL, NULL); desc = gst_pb_utils_get_sink_description (protocol); return gst_installer_detail_new (desc, "urisink", protocol); } /** * gst_missing_element_installer_detail_new: * @factory_name: the name of the missing element (element factory), * e.g. "videoscale" or "cdparanoiasrc" * * Returns an opaque string containing all the details about the missing * element to be passed to an external installer called via * gst_install_plugins_async() or gst_install_plugins_sync(). * * This function is mainly for applications that call external plugin * installation mechanisms using one of the two above-mentioned functions in * the case where the application knows exactly what kind of plugin it is * missing. * * Returns: (transfer full): a newly-allocated detail string. Free string * with g_free() when not needed any longer. */ gchar * gst_missing_element_installer_detail_new (const gchar * factory_name) { gchar *desc; g_return_val_if_fail (factory_name != NULL, NULL); desc = gst_pb_utils_get_element_description (factory_name); return gst_installer_detail_new (desc, "element", factory_name); } /** * gst_missing_decoder_installer_detail_new: * @decode_caps: the (fixed) caps for which a decoder element is needed * * Returns an opaque string containing all the details about the missing * element to be passed to an external installer called via * gst_install_plugins_async() or gst_install_plugins_sync(). * * This function is mainly for applications that call external plugin * installation mechanisms using one of the two above-mentioned functions in * the case where the application knows exactly what kind of plugin it is * missing. * * Returns: (transfer full): a newly-allocated detail string. Free string * with g_free() when not needed any longer. */ gchar * gst_missing_decoder_installer_detail_new (const GstCaps * decode_caps) { GstCaps *caps; gchar *detail_str, *caps_str, *desc; g_return_val_if_fail (decode_caps != NULL, NULL); g_return_val_if_fail (GST_IS_CAPS (decode_caps), NULL); g_return_val_if_fail (!gst_caps_is_any (decode_caps), NULL); g_return_val_if_fail (!gst_caps_is_empty (decode_caps), NULL); g_return_val_if_fail (gst_caps_is_fixed (decode_caps), NULL); desc = gst_pb_utils_get_decoder_description (decode_caps); caps = copy_and_clean_caps (decode_caps); caps_str = gst_caps_to_string (caps); detail_str = gst_installer_detail_new (desc, "decoder", caps_str); g_free (caps_str); gst_caps_unref (caps); return detail_str; } /** * gst_missing_encoder_installer_detail_new: * @encode_caps: the (fixed) caps for which an encoder element is needed * * Returns an opaque string containing all the details about the missing * element to be passed to an external installer called via * gst_install_plugins_async() or gst_install_plugins_sync(). * * This function is mainly for applications that call external plugin * installation mechanisms using one of the two above-mentioned functions in * the case where the application knows exactly what kind of plugin it is * missing. * * Returns: (transfer full): a newly-allocated detail string. Free string * with g_free() when not needed any longer. */ gchar * gst_missing_encoder_installer_detail_new (const GstCaps * encode_caps) { GstCaps *caps; gchar *detail_str, *caps_str, *desc; g_return_val_if_fail (encode_caps != NULL, NULL); g_return_val_if_fail (GST_IS_CAPS (encode_caps), NULL); g_return_val_if_fail (!gst_caps_is_any (encode_caps), NULL); g_return_val_if_fail (!gst_caps_is_empty (encode_caps), NULL); g_return_val_if_fail (gst_caps_is_fixed (encode_caps), NULL); desc = gst_pb_utils_get_encoder_description (encode_caps); caps = copy_and_clean_caps (encode_caps); caps_str = gst_caps_to_string (caps); detail_str = gst_installer_detail_new (desc, "encoder", caps_str); g_free (caps_str); gst_caps_unref (caps); return detail_str; }