• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu>
3  *               2001 Steve Baker <stevebaker_org@yahoo.co.uk>
4  *               2003 Andy Wingo <wingo at pobox.com>
5  *               2016 Thibault Saunier <thibault.saunier@collabora.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 /**
24  * SECTION:element-lv2
25  * @title: lv2
26  * @short_description: bridge for LV2.
27  *
28  * LV2 is a standard for plugins and matching host applications,
29  * mainly targeted at audio processing and generation.  It is intended as
30  * a successor to LADSPA (Linux Audio Developer's Simple Plugin API).
31  *
32  * The LV2 element is a bridge for plugins using the
33  * <ulink url="http://www.lv2plug.in/">LV2</ulink> API.  It scans all
34  * installed LV2 plugins and registers them as gstreamer elements.
35  */
36 
37 #ifdef HAVE_CONFIG_H
38 #include "config.h"
39 #endif
40 
41 #include <string.h>
42 #include "gstlv2.h"
43 
44 #include <gst/audio/audio-channels.h>
45 #include <lv2/lv2plug.in/ns/ext/port-groups/port-groups.h>
46 #include "lv2/lv2plug.in/ns/ext/event/event.h"
47 #include "lv2/lv2plug.in/ns/ext/presets/presets.h"
48 #include "lv2/lv2plug.in/ns/ext/state/state.h"
49 
50 GST_DEBUG_CATEGORY (lv2_debug);
51 #define GST_CAT_DEFAULT lv2_debug
52 
53 #if defined (G_OS_WIN32)
54 #define GST_LV2_ENVVARS "APPDATA/LV2:COMMONPROGRAMFILES/LV2"
55 #define GST_LV2_DEFAULT_PATH NULL
56 #elif defined (HAVE_OSX)
57 #define GST_LV2_ENVVARS "HOME/Library/Audio/Plug-Ins/LV2:HOME/.lv2"
58 #define GST_LV2_DEFAULT_PATH \
59   "/usr/local/lib/lv2:/usr/lib/lv2:/Library/Audio/Plug-Ins/LV2"
60 #elif defined (G_OS_UNIX)
61 #define GST_LV2_ENVVARS "HOME/.lv2"
62 #define GST_LV2_DEFAULT_PATH \
63   "/usr/lib/lv2:" \
64   "/usr/lib64/lv2:" \
65   "/usr/local/lib/lv2:" \
66   "/usr/local/lib64/lv2:" \
67   LIBDIR "/lv2"
68 #else
69 #error "Unsupported OS"
70 #endif
71 
72 GstStructure *lv2_meta_all = NULL;
73 
74 static void
lv2_plugin_register_element(GstPlugin * plugin,GstStructure * lv2_meta)75 lv2_plugin_register_element (GstPlugin * plugin, GstStructure * lv2_meta)
76 {
77   guint audio_in, audio_out;
78 
79   gst_structure_get_uint (lv2_meta, "audio-in", &audio_in);
80   gst_structure_get_uint (lv2_meta, "audio-out", &audio_out);
81 
82   if (audio_in == 0) {
83     gst_lv2_source_register_element (plugin, lv2_meta);
84   } else {
85     gst_lv2_filter_register_element (plugin, lv2_meta);
86   }
87 }
88 
89 static void
lv2_count_ports(const LilvPlugin * lv2plugin,guint * audio_in,guint * audio_out,guint * control)90 lv2_count_ports (const LilvPlugin * lv2plugin, guint * audio_in,
91     guint * audio_out, guint * control)
92 {
93   GHashTable *port_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
94       g_free, NULL);
95   guint i;
96 
97   *audio_in = *audio_out = *control = 0;
98   for (i = 0; i < lilv_plugin_get_num_ports (lv2plugin); i++) {
99     const LilvPort *port = lilv_plugin_get_port_by_index (lv2plugin, i);
100 
101     if (lilv_port_is_a (lv2plugin, port, audio_class)) {
102       const gboolean is_input = lilv_port_is_a (lv2plugin, port, input_class);
103       LilvNodes *lv2group = lilv_port_get (lv2plugin, port, group_pred);
104 
105       if (lv2group) {
106         const gchar *uri = lilv_node_as_uri (lv2group);
107 
108         if (g_hash_table_contains (port_groups, uri))
109           continue;
110 
111         g_hash_table_add (port_groups, g_strdup (uri));
112         lilv_node_free (lv2group);
113       }
114 
115       if (is_input)
116         (*audio_in)++;
117       else
118         (*audio_out)++;
119     } else if (lilv_port_is_a (lv2plugin, port, control_class) ||
120         lilv_port_is_a (lv2plugin, port, cv_class)) {
121       (*control)++;
122     }
123   }
124   g_hash_table_unref (port_groups);
125 }
126 
127 /* search the plugin path */
128 static gboolean
lv2_plugin_discover(GstPlugin * plugin)129 lv2_plugin_discover (GstPlugin * plugin)
130 {
131   guint audio_in, audio_out, control;
132   LilvIter *i;
133   const LilvPlugins *plugins = lilv_world_get_all_plugins (world);
134 
135   for (i = lilv_plugins_begin (plugins); !lilv_plugins_is_end (plugins, i);
136       i = lilv_plugins_next (plugins, i)) {
137     GstStructure *lv2_meta = NULL;
138     GValue value = { 0, };
139     const LilvPlugin *lv2plugin = lilv_plugins_get (plugins, i);
140     const gchar *plugin_uri, *p;
141     gchar *type_name;
142     gboolean can_do_presets;
143 
144     plugin_uri = lilv_node_as_uri (lilv_plugin_get_uri (lv2plugin));
145 
146     /* check if we support the required host features */
147     if (!gst_lv2_check_required_features (lv2plugin)) {
148       GST_FIXME ("lv2 plugin %s needs host features", plugin_uri);
149       continue;
150     }
151 
152     /* construct the type name from plugin URI */
153     if ((p = strstr (plugin_uri, "://"))) {
154       /* cut off the protocol (e.g. http://) */
155       type_name = g_strdup (&p[3]);
156     } else {
157       type_name = g_strdup (plugin_uri);
158     }
159     g_strcanon (type_name, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "-+", '-');
160 
161     /* if it's already registered, drop it */
162     if (g_type_from_name (type_name))
163       goto next;
164 
165     /* check if this has any audio ports */
166     lv2_count_ports (lv2plugin, &audio_in, &audio_out, &control);
167 
168     if (audio_in == 0 && audio_out == 0) {
169       GST_FIXME ("plugin %s has no audio pads", type_name);
170       goto next;
171     } else if (audio_in == 0) {
172       if (audio_out != 1) {
173         GST_FIXME ("plugin %s is not a GstBaseSrc (num_src_pads: %d)",
174             type_name, audio_out);
175         goto next;
176       }
177     } else if (audio_out == 0) {
178       GST_FIXME ("plugin %s is a sink element (num_sink_pads: %d"
179           " num_src_pads: %d)", type_name, audio_in, audio_out);
180       goto next;
181     } else {
182       if (audio_in != 1 || audio_out != 1) {
183         GST_FIXME ("plugin %s is not a GstAudioFilter (num_sink_pads: %d"
184             " num_src_pads: %d)", type_name, audio_in, audio_out);
185         goto next;
186       }
187     }
188 
189     /* check supported extensions */
190     can_do_presets = lilv_plugin_has_extension_data (lv2plugin, state_iface)
191         || lilv_plugin_has_feature (lv2plugin, state_uri)
192         || (control > 0);
193     GST_INFO ("plugin %s can%s do presets", type_name,
194         (can_do_presets ? "" : "'t"));
195 
196     lv2_meta = gst_structure_new ("lv2",
197         "element-uri", G_TYPE_STRING, plugin_uri,
198         "element-type-name", G_TYPE_STRING, type_name,
199         "audio-in", G_TYPE_UINT, audio_in,
200         "audio-out", G_TYPE_UINT, audio_out,
201         "can-do-presets", G_TYPE_BOOLEAN, can_do_presets, NULL);
202 
203     g_value_init (&value, GST_TYPE_STRUCTURE);
204     g_value_set_boxed (&value, lv2_meta);
205     gst_structure_set_value (lv2_meta_all, type_name, &value);
206     g_value_unset (&value);
207 
208     // don't free type_name
209     continue;
210 
211   next:
212     g_free (type_name);
213   }
214 
215   return TRUE;
216 }
217 
218 static gboolean
plugin_init(GstPlugin * plugin)219 plugin_init (GstPlugin * plugin)
220 {
221   gboolean res = FALSE;
222   gint n = 0;
223 
224   GST_DEBUG_CATEGORY_INIT (lv2_debug, "lv2",
225       GST_DEBUG_FG_GREEN | GST_DEBUG_BG_BLACK | GST_DEBUG_BOLD, "LV2");
226 
227   world = lilv_world_new ();
228   lilv_world_load_all (world);
229   gst_lv2_host_init ();
230 
231 /* have been added after lilv-0.22.0, which is the last release */
232 #ifndef LILV_URI_ATOM_PORT
233 #define LILV_URI_ATOM_PORT    "http://lv2plug.in/ns/ext/atom#AtomPort"
234 #endif
235 #ifndef LILV_URI_CV_PORT
236 #define LILV_URI_CV_PORT      "http://lv2plug.in/ns/lv2core#CVPort"
237 #endif
238 
239   atom_class = lilv_new_uri (world, LILV_URI_ATOM_PORT);
240   audio_class = lilv_new_uri (world, LILV_URI_AUDIO_PORT);
241   control_class = lilv_new_uri (world, LILV_URI_CONTROL_PORT);
242   cv_class = lilv_new_uri (world, LILV_URI_CV_PORT);
243   event_class = lilv_new_uri (world, LILV_URI_EVENT_PORT);
244   input_class = lilv_new_uri (world, LILV_URI_INPUT_PORT);
245   output_class = lilv_new_uri (world, LILV_URI_OUTPUT_PORT);
246   preset_class = lilv_new_uri (world, LV2_PRESETS__Preset);
247   state_iface = lilv_new_uri (world, LV2_STATE__interface);
248   state_uri = lilv_new_uri (world, LV2_STATE_URI);
249 
250   integer_prop = lilv_new_uri (world, LV2_CORE__integer);
251   toggled_prop = lilv_new_uri (world, LV2_CORE__toggled);
252   designation_pred = lilv_new_uri (world, LV2_CORE__designation);
253   in_place_broken_pred = lilv_new_uri (world, LV2_CORE__inPlaceBroken);
254   optional_pred = lilv_new_uri (world, LV2_CORE__optionalFeature);
255   group_pred = lilv_new_uri (world, LV2_PORT_GROUPS__group);
256   supports_event_pred = lilv_new_uri (world, LV2_EVENT__supportsEvent);
257   label_pred = lilv_new_uri (world, LILV_NS_RDFS "label");
258 
259   center_role = lilv_new_uri (world, LV2_PORT_GROUPS__center);
260   left_role = lilv_new_uri (world, LV2_PORT_GROUPS__left);
261   right_role = lilv_new_uri (world, LV2_PORT_GROUPS__right);
262   rear_center_role = lilv_new_uri (world, LV2_PORT_GROUPS__rearCenter);
263   rear_left_role = lilv_new_uri (world, LV2_PORT_GROUPS__rearLeft);
264   rear_right_role = lilv_new_uri (world, LV2_PORT_GROUPS__rearLeft);
265   lfe_role = lilv_new_uri (world, LV2_PORT_GROUPS__lowFrequencyEffects);
266   center_left_role = lilv_new_uri (world, LV2_PORT_GROUPS__centerLeft);
267   center_right_role = lilv_new_uri (world, LV2_PORT_GROUPS__centerRight);
268   side_left_role = lilv_new_uri (world, LV2_PORT_GROUPS__sideLeft);
269   side_right_role = lilv_new_uri (world, LV2_PORT_GROUPS__sideRight);
270 
271   gst_plugin_add_dependency_simple (plugin,
272       "LV2_PATH:" GST_LV2_ENVVARS, GST_LV2_DEFAULT_PATH, NULL,
273       GST_PLUGIN_DEPENDENCY_FLAG_RECURSE);
274 
275   /* ensure GstAudioChannelPosition type is registered */
276   if (!gst_audio_channel_position_get_type ())
277     return FALSE;
278 
279   lv2_meta_all = (GstStructure *) gst_plugin_get_cache_data (plugin);
280   if (lv2_meta_all) {
281     n = gst_structure_n_fields (lv2_meta_all);
282   }
283   GST_INFO_OBJECT (plugin, "%d entries in cache", n);
284   if (!n) {
285     lv2_meta_all = gst_structure_new_empty ("lv2");
286     if ((res = lv2_plugin_discover (plugin))) {
287       n = gst_structure_n_fields (lv2_meta_all);
288       GST_INFO_OBJECT (plugin, "%d entries after scanning", n);
289       gst_plugin_set_cache_data (plugin, lv2_meta_all);
290     }
291   } else {
292     res = TRUE;
293   }
294 
295   if (n) {
296     gint i;
297     const gchar *name;
298     const GValue *value;
299 
300     GST_INFO_OBJECT (plugin, "register types");
301 
302     for (i = 0; i < n; i++) {
303       name = gst_structure_nth_field_name (lv2_meta_all, i);
304       value = gst_structure_get_value (lv2_meta_all, name);
305       if (G_VALUE_TYPE (value) == GST_TYPE_STRUCTURE) {
306         GstStructure *lv2_meta = g_value_get_boxed (value);
307 
308         lv2_plugin_register_element (plugin, lv2_meta);
309       }
310     }
311   }
312 
313   if (!res) {
314     GST_WARNING_OBJECT (plugin, "no lv2 plugins found, check LV2_PATH");
315   }
316 
317   /* we don't want to fail, even if there are no elements registered */
318   return TRUE;
319 }
320 
321 #ifdef __GNUC__
322 __attribute__ ((destructor))
323 #endif
plugin_cleanup(GstPlugin * plugin)324      static void plugin_cleanup (GstPlugin * plugin)
325 {
326   lilv_node_free (atom_class);
327   lilv_node_free (audio_class);
328   lilv_node_free (control_class);
329   lilv_node_free (cv_class);
330   lilv_node_free (event_class);
331   lilv_node_free (input_class);
332   lilv_node_free (output_class);
333   lilv_node_free (preset_class);
334   lilv_node_free (state_iface);
335   lilv_node_free (state_uri);
336 
337   lilv_node_free (integer_prop);
338   lilv_node_free (toggled_prop);
339   lilv_node_free (designation_pred);
340   lilv_node_free (in_place_broken_pred);
341   lilv_node_free (optional_pred);
342   lilv_node_free (group_pred);
343   lilv_node_free (supports_event_pred);
344   lilv_node_free (label_pred);
345 
346   lilv_node_free (center_role);
347   lilv_node_free (left_role);
348   lilv_node_free (right_role);
349   lilv_node_free (rear_center_role);
350   lilv_node_free (rear_left_role);
351   lilv_node_free (rear_right_role);
352   lilv_node_free (lfe_role);
353   lilv_node_free (center_left_role);
354   lilv_node_free (center_right_role);
355   lilv_node_free (side_left_role);
356   lilv_node_free (side_right_role);
357 
358   lilv_world_free (world);
359 }
360 
361 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
362     GST_VERSION_MINOR,
363     lv2,
364     "All LV2 plugins",
365     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
366