| /* GStreamer LADSPA plugin |
| * Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu> |
| * 2001 Steve Baker <stevebaker_org@yahoo.co.uk> |
| * 2003 Andy Wingo <wingo at pobox.com> |
| * Copyright (C) 2013 Juan Manuel Borges CaƱo <juanmabcmail@gmail.com> |
| * |
| * 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:element-ladspa |
| * @short_description: bridge for LADSPA (Linux Audio Developer's Simple Plugin API) |
| * @see_also: #GstAudioConvert #GstAudioResample, #GstAudioTestSrc, #GstAutoAudioSink |
| * |
| * The LADSPA (Linux Audio Developer's Simple Plugin API) element is a bridge |
| * for plugins using the <ulink url="http://www.ladspa.org/">LADSPA</ulink> API. |
| * It scans all installed LADSPA plugins and registers them as gstreamer |
| * elements. If available it can also parse LRDF files and use the metadata for |
| * element classification. The functionality you get depends on the LADSPA plugins |
| * you have installed. |
| * |
| * <refsect2> |
| * <title>Example LADSPA line without this plugins</title> |
| * |[ |
| * (padsp) listplugins |
| * (padsp) analyseplugin cmt.so amp_mono |
| * gst-launch-1.0 -e filesrc location="$myfile" ! decodebin ! audioconvert ! audioresample ! "audio/x-raw,format=S16LE,rate=48000,channels=1" ! wavenc ! filesink location="testin.wav" |
| * (padsp) applyplugin testin.wav testout.wav cmt.so amp_mono 2 |
| * gst-launch-1.0 playbin uri=file://"$PWD"/testout.wav |
| * ]| Decode any audio file into wav with the format expected for the specific ladspa plugin to be applied, apply the ladspa filter and play it. |
| * </refsect2> |
| * |
| * Now with this plugin: |
| * |
| * <refsect2> |
| * <title>Example LADSPA line with this plugins</title> |
| * |[ |
| * gst-launch-1.0 autoaudiosrc ! ladspa-cmt-so-amp-mono gain=2 ! ladspa-caps-so-plate ! ladspa-tap-echo-so-tap-stereo-echo l-delay=500 r-haas-delay=500 ! tee name=myT myT. ! queue ! autoaudiosink myT. ! queue ! audioconvert ! goom ! videoconvert ! xvimagesink pixel-aspect-ratio=3/4 |
| * ]| Get audio input, filter it through CAPS Plate and TAP Stereo Echo, play it and show a visualization (recommended hearphones). |
| * </refsect2> |
| * |
| * In case you wonder the plugin naming scheme, quoting ladspa.h: |
| * "Plugin types should be identified by file and label rather than by |
| * index or plugin name, which may be changed in new plugin versions." |
| * This is really the best way then, and so it is less prone to conflicts. |
| * |
| * Also it is worth noting that LADSPA provides a control in and out interface, |
| * on top of the audio in and out one, so some parameters are readable too. |
| * |
| * You can see the listing of plugins available with: |
| * <refsect2> |
| * <title>Inspecting the plugins list</title> |
| * |[ |
| * gst-inspect ladspa |
| * ]| List available LADSPA plugins on gstreamer. |
| * </refsect2> |
| * |
| * You can see the parameters of any plugin with: |
| * <refsect2> |
| * <title>Inspecting the plugins</title> |
| * |[ |
| * gst-inspect ladspa-retro-flange-1208-so-retroflange |
| * ]| List details of the plugin, parameters, range and defaults included. |
| * </refsect2> |
| * |
| * The elements categorize in: |
| * <itemizedlist> |
| * <listitem><para>Filter/Effect/Audio/LADSPA:</para> |
| * <refsect2> |
| * <title>Example Filter/Effect/Audio/LADSPA line with this plugins</title> |
| * |[ |
| * gst-launch-1.0 filesrc location="$myfile" ! decodebin ! audioconvert ! audioresample ! ladspa-calf-so-reverb decay-time=15 high-frq-damp=20000 room-size=5 diffusion=1 wet-amount=2 dry-amount=2 pre-delay=50 bass-cut=20000 treble-cut=20000 ! ladspa-tap-echo-so-tap-stereo-echo l-delay=500 r-haas-delay=500 ! autoaudiosink |
| * ]| Decode any audio file, filter it through Calf Reverb LADSPA then TAP Stereo Echo, and play it. |
| * </refsect2> |
| * </listitem> |
| * <listitem><para>Source/Audio/LADSPA:</para> |
| * <refsect2> |
| * <title>Example Source/Audio/LADSPA line with this plugins</title> |
| * |[ |
| * gst-launch-1.0 ladspasrc-sine-so-sine-fcac frequency=220 amplitude=100 ! audioconvert ! autoaudiosink |
| * ]| Generate a sine wave with Sine Oscillator (Freq:control, Amp:control) and play it. |
| * </refsect2> |
| * <refsect2> |
| * <title>Example Source/Audio/LADSPA line with this plugins</title> |
| * |[ |
| * gst-launch-1.0 ladspasrc-caps-so-click bpm=240 volume=1 ! autoaudiosink |
| * ]| Generate clicks with CAPS Click - Metronome at 240 beats per minute and play it. |
| * </refsect2> |
| * <refsect2> |
| * <title>Example Source/Audio/LADSPA line with this plugins</title> |
| * |[ |
| * gst-launch-1.0 ladspasrc-random-1661-so-random-fcsc-oa ! ladspa-cmt-so-amp-mono gain=1.5 ! ladspa-caps-so-plate ! tee name=myT myT. ! queue ! autoaudiosink myT. ! queue ! audioconvert ! wavescope ! videoconvert ! autovideosink |
| * ]| Generate random wave, filter it trhough Mono Amplifier and Versatile Plate Reverb, and play, while showing, it. |
| * </refsect2> |
| * </listitem> |
| * <listitem><para>Sink/Audio/LADSPA:</para> |
| * <refsect2> |
| * <title>Example Sink/Audio/LADSPA line with this plugins</title> |
| * |[ |
| * gst-launch-1.0 autoaudiosrc ! ladspa-cmt-so-amp-mono gain=2 ! ladspa-caps-so-plate ! ladspa-tap-echo-so-tap-stereo-echo l-delay=500 r-haas-delay=500 ! tee name=myT myT. ! audioconvert ! audioresample ! queue ! ladspasink-cmt-so-null-ai myT. ! audioconvert ! audioresample ! queue ! goom ! videoconvert ! xvimagesink pixel-aspect-ratio=3/4 |
| * ]| Get audio input, filter it trhough Mono Amplifier, CAPS Plate LADSPA and TAP Stereo Echo, explicitily anulate audio with Null (Audio Output), and play a visualization (recommended hearphones). |
| * </refsect2> |
| * </listitem> |
| * </itemizedlist> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "gstladspautils.h" |
| #include "gstladspafilter.h" |
| #include "gstladspasource.h" |
| #include "gstladspasink.h" |
| #include <gst/gst-i18n-plugin.h> |
| |
| #include <string.h> |
| #include <ladspa.h> |
| #ifdef HAVE_LRDF |
| #include <lrdf.h> |
| #endif |
| |
| GST_DEBUG_CATEGORY (ladspa_debug); |
| #define GST_CAT_DEFAULT ladspa_debug |
| |
| /* |
| * 1.0 and the 1.1 preliminary headers don't define a version, but |
| * 1.1 finally does |
| */ |
| #ifndef LADSPA_VERSION |
| #define LADSPA_VERSION "1.0" |
| #endif |
| |
| #define GST_LADSPA_DEFAULT_PATH \ |
| "/usr/lib/ladspa" G_SEARCHPATH_SEPARATOR_S \ |
| "/usr/local/lib/ladspa" G_SEARCHPATH_SEPARATOR_S \ |
| LIBDIR "/ladspa" |
| |
| GstStructure *ladspa_meta_all = NULL; |
| |
| static void |
| ladspa_plugin_register_element (GstPlugin * plugin, GstStructure * ladspa_meta) |
| { |
| guint audio_in, audio_out; |
| |
| gst_structure_get_uint (ladspa_meta, "audio-in", &audio_in); |
| gst_structure_get_uint (ladspa_meta, "audio-out", &audio_out); |
| |
| if (audio_in == 0) { |
| ladspa_register_source_element (plugin, ladspa_meta); |
| } else if (audio_out == 0) { |
| ladspa_register_sink_element (plugin, ladspa_meta); |
| } else { |
| ladspa_register_filter_element (plugin, ladspa_meta); |
| } |
| } |
| |
| static void |
| ladspa_count_ports (const LADSPA_Descriptor * descriptor, |
| guint * audio_in, guint * audio_out, guint * control_in, |
| guint * control_out) |
| { |
| guint i; |
| |
| *audio_in = *audio_out = *control_in = *control_out = 0; |
| |
| for (i = 0; i < descriptor->PortCount; i++) { |
| LADSPA_PortDescriptor p = descriptor->PortDescriptors[i]; |
| |
| if (LADSPA_IS_PORT_AUDIO (p)) { |
| if (LADSPA_IS_PORT_INPUT (p)) |
| (*audio_in)++; |
| else |
| (*audio_out)++; |
| } else if (LADSPA_IS_PORT_CONTROL (p)) { |
| if (LADSPA_IS_PORT_INPUT (p)) |
| (*control_in)++; |
| else |
| (*control_out)++; |
| } |
| } |
| } |
| |
| static void |
| ladspa_describe_plugin (const gchar * file_name, const gchar * entry_name, |
| LADSPA_Descriptor_Function descriptor_function) |
| { |
| const LADSPA_Descriptor *desc; |
| guint i; |
| |
| /* walk through all the plugins in this plugin library */ |
| for (i = 0; (desc = descriptor_function (i)); i++) { |
| GstStructure *ladspa_meta = NULL; |
| GValue value = { 0, }; |
| gchar *tmp; |
| gchar *type_name; |
| guint audio_in, audio_out, control_in, control_out; |
| |
| /* count ports of this plugin */ |
| ladspa_count_ports (desc, &audio_in, &audio_out, &control_in, &control_out); |
| |
| /* categorize */ |
| if (audio_in == 0 && audio_out == 0) { |
| GST_WARNING ("Skipping control only element (%s:%lu/%s)", |
| entry_name, desc->UniqueID, desc->Label); |
| continue; |
| } else if (audio_in == 0) { |
| tmp = g_strdup_printf ("ladspasrc-%s-%s", entry_name, desc->Label); |
| } else if (audio_out == 0) { |
| tmp = g_strdup_printf ("ladspasink-%s-%s", entry_name, desc->Label); |
| } else { |
| tmp = g_strdup_printf ("ladspa-%s-%s", entry_name, desc->Label); |
| } |
| type_name = g_ascii_strdown (tmp, -1); |
| g_free (tmp); |
| g_strcanon (type_name, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "-+", '-'); |
| |
| /* check if the type is already registered */ |
| if (g_type_from_name (type_name)) { |
| GST_WARNING ("Plugin identifier collision for %s (%s:%lu/%s)", type_name, |
| entry_name, desc->UniqueID, desc->Label); |
| g_free (type_name); |
| continue; |
| } |
| |
| ladspa_meta = gst_structure_new_empty ("ladspa"); |
| gst_structure_set (ladspa_meta, |
| "plugin-filename", G_TYPE_STRING, file_name, |
| "element-ix", G_TYPE_UINT, i, |
| "element-type-name", G_TYPE_STRING, type_name, |
| "audio-in", G_TYPE_UINT, audio_in, |
| "audio-out", G_TYPE_UINT, audio_out, |
| "control-in", G_TYPE_UINT, control_in, |
| "control-out", G_TYPE_UINT, control_out, NULL); |
| |
| g_value_init (&value, GST_TYPE_STRUCTURE); |
| g_value_set_boxed (&value, ladspa_meta); |
| gst_structure_set_value (ladspa_meta_all, type_name, &value); |
| g_value_unset (&value); |
| } |
| } |
| |
| #ifdef HAVE_LRDF |
| static gboolean |
| ladspa_rdf_directory_search (const char *dir_name) |
| { |
| GDir *dir; |
| gchar *file_name, *file_uri; |
| const gchar *entry_name; |
| gint ok; |
| |
| GST_INFO ("scanning directory for rdfs \"%s\"", dir_name); |
| |
| dir = g_dir_open (dir_name, 0, NULL); |
| if (!dir) |
| return FALSE; |
| |
| while ((entry_name = g_dir_read_name (dir))) { |
| file_name = g_build_filename (dir_name, entry_name, NULL); |
| file_uri = g_strconcat ("file://", file_name, NULL); |
| ok = lrdf_read_file (file_uri); |
| GST_INFO ("read %s : %d", file_uri, ok); |
| g_free (file_uri); |
| g_free (file_name); |
| } |
| g_dir_close (dir); |
| |
| return TRUE; |
| } |
| #endif |
| |
| /* search just the one directory */ |
| static gboolean |
| ladspa_plugin_directory_search (GstPlugin * ladspa_plugin, const char *dir_name) |
| { |
| GDir *dir; |
| gchar *file_name; |
| const gchar *entry_name; |
| LADSPA_Descriptor_Function descriptor_function; |
| GModule *plugin; |
| gboolean ok = FALSE; |
| |
| GST_INFO ("scanning directory for plugins \"%s\"", dir_name); |
| |
| dir = g_dir_open (dir_name, 0, NULL); |
| if (!dir) |
| return FALSE; |
| |
| while ((entry_name = g_dir_read_name (dir))) { |
| file_name = g_build_filename (dir_name, entry_name, NULL); |
| plugin = |
| g_module_open (file_name, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); |
| if (plugin) { |
| /* the file is a shared library */ |
| if (g_module_symbol (plugin, "ladspa_descriptor", |
| (gpointer *) & descriptor_function)) { |
| /* we've found a ladspa_descriptor function, now introspect it. */ |
| GST_INFO ("describe %s", file_name); |
| ladspa_describe_plugin (file_name, entry_name, descriptor_function); |
| ok = TRUE; |
| } else { |
| /* it was a library, but not a LADSPA one. Unload it. */ |
| g_module_close (plugin); |
| } |
| } |
| g_free (file_name); |
| } |
| g_dir_close (dir); |
| |
| return ok; |
| } |
| |
| /* search the plugin path */ |
| static gboolean |
| ladspa_plugin_path_search (GstPlugin * plugin) |
| { |
| const gchar *search_path; |
| gchar *ladspa_path; |
| gchar **paths; |
| gint i, j, path_entries; |
| gboolean res = FALSE, skip; |
| #ifdef HAVE_LRDF |
| gchar *pos, *prefix, *rdf_path; |
| #endif |
| |
| search_path = g_getenv ("LADSPA_PATH"); |
| if (search_path) { |
| ladspa_path = |
| g_strdup_printf ("%s" G_SEARCHPATH_SEPARATOR_S GST_LADSPA_DEFAULT_PATH, |
| search_path); |
| } else { |
| ladspa_path = g_strdup (GST_LADSPA_DEFAULT_PATH); |
| } |
| |
| paths = g_strsplit (ladspa_path, G_SEARCHPATH_SEPARATOR_S, 0); |
| path_entries = g_strv_length (paths); |
| GST_INFO ("%d dirs in search paths \"%s\"", path_entries, ladspa_path); |
| |
| #ifdef HAVE_LRDF |
| for (i = 0; i < path_entries; i++) { |
| skip = FALSE; |
| for (j = 0; j < i; j++) { |
| if (!strcmp (paths[i], paths[j])) { |
| skip = TRUE; |
| break; |
| } |
| } |
| if (skip) |
| break; |
| /* |
| * transform path: /usr/lib/ladspa -> /usr/share/ladspa/rdf/ |
| * yes, this is ugly, but lrdf has not searchpath |
| */ |
| if ((pos = strstr (paths[i], "/lib/ladspa"))) { |
| prefix = g_strndup (paths[i], (pos - paths[i])); |
| rdf_path = g_build_filename (prefix, "share", "ladspa", "rdf", NULL); |
| ladspa_rdf_directory_search (rdf_path); |
| g_free (rdf_path); |
| g_free (prefix); |
| } |
| } |
| #endif |
| |
| for (i = 0; i < path_entries; i++) { |
| skip = FALSE; |
| for (j = 0; j < i; j++) { |
| if (!strcmp (paths[i], paths[j])) { |
| skip = TRUE; |
| break; |
| } |
| } |
| if (skip) |
| break; |
| res |= ladspa_plugin_directory_search (plugin, paths[i]); |
| } |
| g_strfreev (paths); |
| |
| g_free (ladspa_path); |
| |
| return res; |
| } |
| |
| static gboolean |
| plugin_init (GstPlugin * plugin) |
| { |
| gboolean res = FALSE; |
| gint n = 0; |
| |
| GST_DEBUG_CATEGORY_INIT (ladspa_debug, "ladspa", 0, "LADSPA plugins"); |
| |
| #ifdef ENABLE_NLS |
| GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE, |
| LOCALEDIR); |
| bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); |
| bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); |
| #endif |
| |
| gst_plugin_add_dependency_simple (plugin, |
| "LADSPA_PATH", |
| GST_LADSPA_DEFAULT_PATH, NULL, GST_PLUGIN_DEPENDENCY_FLAG_NONE); |
| |
| #ifdef HAVE_LRDF |
| lrdf_init (); |
| #endif |
| |
| ladspa_meta_all = (GstStructure *) gst_plugin_get_cache_data (plugin); |
| if (ladspa_meta_all) { |
| n = gst_structure_n_fields (ladspa_meta_all); |
| } |
| GST_INFO ("%d entries in cache", n); |
| if (!n) { |
| ladspa_meta_all = gst_structure_new_empty ("ladspa"); |
| res = ladspa_plugin_path_search (plugin); |
| if (res) { |
| n = gst_structure_n_fields (ladspa_meta_all); |
| GST_INFO ("%d entries after scanning", n); |
| gst_plugin_set_cache_data (plugin, ladspa_meta_all); |
| } |
| } else { |
| res = TRUE; |
| } |
| |
| if (n) { |
| gint i; |
| const gchar *name; |
| const GValue *value; |
| |
| GST_INFO ("register types"); |
| |
| for (i = 0; i < n; i++) { |
| name = gst_structure_nth_field_name (ladspa_meta_all, i); |
| value = gst_structure_get_value (ladspa_meta_all, name); |
| if (G_VALUE_TYPE (value) == GST_TYPE_STRUCTURE) { |
| GstStructure *ladspa_meta = g_value_get_boxed (value); |
| |
| ladspa_plugin_register_element (plugin, ladspa_meta); |
| } |
| } |
| } |
| |
| if (!res) { |
| GST_WARNING ("no LADSPA plugins found, check LADSPA_PATH"); |
| } |
| |
| /* we don't want to fail, even if there are no elements registered */ |
| return TRUE; |
| } |
| |
| GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, |
| GST_VERSION_MINOR, |
| ladspa, |
| "LADSPA plugin", |
| plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) |