changeset 14217:5b5ebf93ec16

Adds support for LADSPA (Linux Audio Developer's Simple Plugin API) plugins. Compilation is optional and can be controled by configure. You need to have the LADSPA SDK installed in order to have it autodetected by configure. Manual page is updated.
author ivo
date Thu, 23 Dec 2004 02:09:52 +0000
parents 0d4589ab4d28
children e85366ab4bd1
files AUTHORS DOCS/man/en/mplayer.1 DOCS/tech/MAINTAINERS configure help/help_mp-en.h libaf/.cvsignore libaf/Makefile libaf/af.c libaf/af_ladspa.c
diffstat 9 files changed, 1095 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/AUTHORS	Thu Dec 23 02:09:49 2004 +0000
+++ b/AUTHORS	Thu Dec 23 02:09:52 2004 +0000
@@ -611,6 +611,7 @@
     * vo_jpeg suboptions parser
     * vo_pnm video output driver
     * vo_md5sum video output driver
+    * af_ladspa LADSPA plugin loader
 
 Ran, Lu <hephooey@fastmail.fm>
     * Chinese documentation translation
--- a/DOCS/man/en/mplayer.1	Thu Dec 23 02:09:49 2004 +0000
+++ b/DOCS/man/en/mplayer.1	Thu Dec 23 02:09:52 2004 +0000
@@ -3633,6 +3633,30 @@
 .B volnorm
 Maximizes the volume without distorting the sound.
 .
+.TP
+.B ladspa=file:label[:controls...]
+Load a LADSPA (Linux Audio Developer's Simple Plugin API) plugin.
+This filter is reentrant, so multiple LADSPA plugins can be used at once.
+.PD 0
+.RSs
+.IPs file\ 
+Specifies the LADSPA plugin library file.
+If LADSPA_PATH is set, it searches for the specified file.
+If it is not set, you must supply a fully specified pathname.
+.IPs label
+Specifies the filter within the library.
+Some libraries contain only one filter, but others contain many of them.
+Entering 'help' here, will list all available filters within the specified
+library, which eliminates the use of 'listplugins' from the LADSPA SDK.
+.IPs controls
+Controls are zero or more floating point values that determine the
+behaviour of the loaded plugin (for example delay, threshold or gain).
+In verbose mode (add -v to the MPlayer command line), all available controls
+and their valid ranges are printed.
+This eliminates the use of 'analyseplugin' from the LADSPA SDK.
+.RE
+.PD 1
+.
 .
 .
 .SH "VIDEO FILTERS"
--- a/DOCS/tech/MAINTAINERS	Thu Dec 23 02:09:49 2004 +0000
+++ b/DOCS/tech/MAINTAINERS	Thu Dec 23 02:09:52 2004 +0000
@@ -178,3 +178,6 @@
     * ao_sgi.c - None
     * ao_sun.c - None
     * ao_win32.c - Sascha Sommer
+
+audio filters:
+    * af_ladspa.c - Ivo van Poorten
--- a/configure	Thu Dec 23 02:09:49 2004 +0000
+++ b/configure	Thu Dec 23 02:09:52 2004 +0000
@@ -211,6 +211,7 @@
   --disable-internal-matroska disable internal Matroska support [enabled]
   --enable-external-faad build with external FAAD2 (AAC) support [autodetect]
   --disable-internal-faad disable internal FAAD2 (AAC) support [autodetect]
+  --disable-ladspa       disable LADSPA plugin support [autodetect]
   --disable-libdv        disable libdv 0.9.5 en/decoding support [autodetect]
   --disable-mad          disable libmad (MPEG audio) support [autodetect]
   --disable-toolame      disable Toolame (MPEG layer 2 audio) support in mencoder [autodetect]
@@ -1317,6 +1318,7 @@
 _tremor=no
 _faad_internal=auto
 _faad_external=auto
+_ladspa=auto
 _xmms=no
 # dvdnav disabled, it does not work
 #_dvdnav=no
@@ -1509,6 +1511,8 @@
   --disable-internal-faad)	_faad_internal=no	;;
   --enable-external-faad)	_faad_external=yes	_faad_internal=no	;;
   --disable-external-faad)	_faad_external=no	;;
+  --enable-ladspa)	_ladspa=yes	;;
+  --disable-ladspa)	_ladspa=no		;;
   --enable-xmms)	_xmms=yes	;;
   --disable-xmms)	_xmms=no	;;
   --enable-dvdread)	_dvdread=yes	;;
@@ -5305,6 +5309,31 @@
   _ld_faad=
 fi
 
+
+echocheck "LADSPA plugin support"
+if test "$_ladspa" = auto ; then
+  cat > $TMPC <<EOF
+#include <stdio.h>
+#include <ladspa.h>
+int main(void) {
+const LADSPA_Descriptor *ld = NULL;
+return 0;
+}
+EOF
+  _ladspa=no
+  cc_check && _ladspa=yes
+fi
+if test "$_ladspa" = yes; then
+  _def_ladspa="#define HAVE_LADSPA"
+  _afsrc="$_afsrc af_ladspa.c"
+  _afmodules="ladspa $_afmodules"
+else
+  _def_ladspa="#undef HAVE_LADSPA"
+  _noafmodules="ladspa $_noafmodules"
+fi
+echores "$_ladspa"
+
+
 if test "$_win32" = auto ; then
   if x86 ; then
     qnx && _win32=no
@@ -7175,6 +7204,9 @@
 $_def_faad_internal
 $_def_faad_version
 
+/* enable LADSPA plugin support */
+$_def_ladspa
+
 /* enable network */
 $_def_network
 
@@ -7307,6 +7339,16 @@
 
 #############################################################################
 
+echo "Creating libaf/config.mak"
+_afobj=`echo $_afsrc | sed -e 's/\.c/\.o/g'`
+cat > libaf/config.mak << EOF
+include ../config.mak
+OPTIONAL_SRCS = $_afsrc
+OPTIONAL_OBJS = $_afobj
+EOF
+
+#############################################################################
+
 cat << EOF
 
 Config files successfully generated by ./configure !
@@ -7333,11 +7375,13 @@
     Codecs: $_codecmodules
     Audio output: $_aomodules
     Video output: $_vomodules
+    Audio filters: $_afmodules
   Disabled optional drivers:
     Input: $_noinputmodules
     Codecs: $_nocodecmodules
     Audio output: $_noaomodules
     Video output: $_novomodules
+    Audio filters: $_noafmodules
 
 'config.h' and 'config.mak' contain your configuration options.
 Note: If you alter theses files (for instance CFLAGS) MPlayer may no longer
--- a/help/help_mp-en.h	Thu Dec 23 02:09:49 2004 +0000
+++ b/help/help_mp-en.h	Thu Dec 23 02:09:52 2004 +0000
@@ -1024,3 +1024,25 @@
 // ao_plugin.c
 
 #define MSGTR_AO_PLUGIN_InvalidPlugin "[AO PLUGIN] invalid plugin: %s\n"
+
+// ======================= AF Audio Filters ================================
+
+// libaf 
+
+// af_ladspa.c
+
+#define MSGTR_AF_LADSPA_AvailableLabels "available labels in"
+#define MSGTR_AF_LADSPA_WarnNoInputs "WARNING! This LADSPA plugin has no audio inputs.\n  The incoming audio signal will be lost."
+#define MSGTR_AF_LADSPA_ErrMultiChannel "Multi-channel (>2) plugins are not supported (yet).\n  Use only mono and stereo plugins."
+#define MSGTR_AF_LADSPA_ErrNoOutputs "This LADSPA plugin has no audio outputs."
+#define MSGTR_AF_LADSPA_ErrInOutDiff "The number of audio inputs and audio outputs of the LADSPA plugin differ."
+#define MSGTR_AF_LADSPA_ErrFailedToLoad "failed to load"
+#define MSGTR_AF_LADSPA_ErrNoDescriptor "Couldn't find ladspa_descriptor() function in the specified library file."
+#define MSGTR_AF_LADSPA_ErrLabelNotFound "Couldn't find label in plugin library."
+#define MSGTR_AF_LADSPA_ErrNoSuboptions "No suboptions specified"
+#define MSGTR_AF_LADSPA_ErrNoLibFile "No library file specified"
+#define MSGTR_AF_LADSPA_ErrNoLabel "No filter label specified"
+#define MSGTR_AF_LADSPA_ErrNotEnoughControls "Not enough controls specified on the command line"
+#define MSGTR_AF_LADSPA_ErrControlBelow "%s: Input control #%d is below lower boundary of %0.4f.\n"
+#define MSGTR_AF_LADSPA_ErrControlAbove "%s: Input control #%d is above upper boundary of %0.4f.\n"
+
--- a/libaf/.cvsignore	Thu Dec 23 02:09:49 2004 +0000
+++ b/libaf/.cvsignore	Thu Dec 23 02:09:52 2004 +0000
@@ -1,1 +1,2 @@
 .depend
+config.mak
--- a/libaf/Makefile	Thu Dec 23 02:09:49 2004 +0000
+++ b/libaf/Makefile	Thu Dec 23 02:09:52 2004 +0000
@@ -1,11 +1,11 @@
-include ../config.mak
+include config.mak
 
 LIBNAME = libaf.a
 
 SRCS=af.c af_mp.c af_dummy.c af_delay.c af_channels.c af_format.c af_resample.c \
 window.c filter.c af_volume.c af_equalizer.c af_tools.c af_comp.c af_gate.c \
 af_pan.c af_surround.c af_sub.c af_export.c af_volnorm.c af_extrastereo.c \
-af_lavcresample.c af_sweep.c af_hrtf.c
+af_lavcresample.c af_sweep.c af_hrtf.c $(OPTIONAL_SRCS)
 
 OBJS=$(SRCS:.c=.o)
 
--- a/libaf/af.c	Thu Dec 23 02:09:49 2004 +0000
+++ b/libaf/af.c	Thu Dec 23 02:09:52 2004 +0000
@@ -28,6 +28,7 @@
 extern af_info_t af_info_lavcresample;
 extern af_info_t af_info_sweep;
 extern af_info_t af_info_hrtf;
+extern af_info_t af_info_ladspa;
 
 static af_info_t* filter_list[]={ 
    &af_info_dummy,
@@ -52,6 +53,9 @@
 #endif
    &af_info_sweep,
    &af_info_hrtf,
+#ifdef HAVE_LADSPA
+   &af_info_ladspa,
+#endif
    NULL 
 };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libaf/af_ladspa.c	Thu Dec 23 02:09:52 2004 +0000
@@ -0,0 +1,994 @@
+/* ------------------------------------------------------------------------- */
+
+/*
+ * af_ladspa.c, LADSPA plugin loader
+ *
+ * Written by Ivo van Poorten <ivop@euronet.nl>
+ * Copyright (C) 2004, 2005
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ *
+ * Changelog
+ *
+ * 2004-12-23   Added to CVS
+ * 2004-12-22   Cleaned up cosmetics
+ *              Made conversion loops in play() more cache-friendly
+ * 2004-12-20   Fixed bug for stereo effect on mono signal
+ *                  (trivial >1 to >=1 change; would segfault otherwise :-) )
+ *              Removed trailing whitespace and fixed warn/err messages
+ *              Have CONTROL_REINIT return a proper value
+ * 2004-12-13   More Doxygen comments
+ * 2004-12-12   Made af_ladspa optional (updated configure, af.c, etc.)
+ * 2004-12-11   Added deactivate and cleanup to uninit.
+ *              Finished Doxygen comments.
+ *              Moved translatable messages to help_mp-en.h
+ * 2004-12-10   Added ranges to list of controls for ease of use.
+ *              Fixed sig11 bug. Implemented (dummy) outputcontrols. Some
+ *              perfectly normal audio processing filters also have output
+ *              controls.
+ * 2004-12-08   Added support for generators (no input, one output)
+ *              Added support for stereo effects
+ *              Added LADSPA_PATH support!
+ * 2004-12-07   Fixed changing buffersize. Now it's really working, also in
+ *              real-time.
+ * 2004-12-06   First working version, mono-effects (1 input --> 1 output) only
+ * 2004-12-05   Started, Loading of plugin/label, Check inputs/outputs/controls
+ *              Due to lack of documentation, I studied the ladspa_sdk source
+ *              code and the loader code of Audacity (by Dominic Mazzoni). So,
+ *              certain similarities in (small) pieces of code are not
+ *              coincidental :-) No C&P jobs though!
+ *
+ */
+
+/* ------------------------------------------------------------------------- */
+
+/* Global Includes */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <inttypes.h>
+#include <math.h>
+#include <limits.h>
+
+#include <dlfcn.h>
+#include <ladspa.h>
+
+/* ------------------------------------------------------------------------- */
+
+/* Local Includes */
+
+#include "af.h"
+#include "../mp_msg.h"
+#include "../help_mp.h"
+
+/* ------------------------------------------------------------------------- */
+
+/* Defines */
+
+/* why, o why, wasn't it defined like this in the first place? */
+
+#define MSGT_AF MSGT_AFILTER
+
+/* ------------------------------------------------------------------------- */
+
+/* Filter specific data */
+
+typedef struct af_ladspa_s
+{
+    int status;     /**< Status of the filter.
+                     *   Either AF_OK or AF_ERROR
+                     *   Because MPlayer re-inits audio filters that
+                     *   _clearly_ returned AF_ERROR anyway, I use this
+                     *   in play() to skip the processing and return
+                     *   the data unchanged.
+                     */
+
+    int activated;  /**< 0 or 1. Activate LADSPA filters only once, even
+                     *   if the buffers get resized, to avoid a stuttering
+                     *   filter.
+                     */
+
+    char *file;
+    char *label;
+
+    char *myname;   /**< It's easy to have a concatenation of file and label */
+
+    void *libhandle;
+    const LADSPA_Descriptor *plugin_descriptor;
+
+    int nports;
+
+    int ninputs;
+    int *inputs;
+
+    int noutputs;
+    int *outputs;
+
+    int ninputcontrols;
+    int *inputcontrolsmap;  /**< Map input port number [0-] to actual port */
+    float *inputcontrols;
+
+    int noutputcontrols;
+    int *outputcontrolsmap;
+    float *outputcontrols;
+
+    int nch;                /**< number of channels */
+    int bufsize;
+    float **inbufs;
+    float **outbufs;
+    LADSPA_Handle *chhandles;
+
+} af_ladspa_t;
+
+/* ------------------------------------------------------------------------- */
+
+static int open(af_instance_t *af);
+static int af_ladspa_malloc_failed(char*);
+
+/* ------------------------------------------------------------------------- */
+
+/* Description */
+
+af_info_t af_info_ladspa = {
+    "LADSPA plugin loader",
+    "ladspa",
+    "Ivo van Poorten",
+    "",
+    AF_FLAGS_REENTRANT,
+    open
+};
+
+/* ------------------------------------------------------------------------- */
+
+/* By lack of a better word (in my vocabulary) this is called 'parse'.
+ * Feel free to suggest an alternative.
+ */
+
+/** \brief Check for inputs, outputs and controls of a given filter.
+ *
+ * This function counts and checks all input, output and control ports
+ * of the filter that was loaded. If it turns out to be a valid
+ * filter for MPlayer use, it prints out a list of all controls and
+ * the corresponding range of its value at message level  MSGL_V.
+ *
+ * \param setup     Current setup of the filter. Must have its
+ *                  plugin_descriptor set!
+ *
+ * \return  Returns AF_OK if it has a valid input/output/controls
+ *          configuration. Else, it returns AF_ERROR.
+ */
+
+static int af_ladspa_parse_plugin(af_ladspa_t *setup) {
+    int p, i;
+    const LADSPA_Descriptor *pdes = setup->plugin_descriptor;
+    LADSPA_PortDescriptor d;
+    LADSPA_PortRangeHint hint;
+
+    if (!setup->libhandle)
+        return AF_ERROR; /* only call parse after a succesful load */
+    if (!setup->plugin_descriptor)
+        return AF_ERROR; /* same as above */
+
+    /* let's do it */
+
+    setup->nports = pdes->PortCount;
+
+    /* allocate memory for all inputs/outputs/controls */
+
+    setup->inputs = calloc(setup->nports, sizeof(int));
+    if (!setup->inputs) return af_ladspa_malloc_failed(setup->myname);
+
+    setup->outputs = calloc(setup->nports, sizeof(int));
+    if (!setup->outputs) return af_ladspa_malloc_failed(setup->myname);
+
+    setup->inputcontrolsmap = calloc(setup->nports, sizeof(int));
+    if (!setup->inputcontrolsmap) return af_ladspa_malloc_failed(setup->myname);
+
+    setup->inputcontrols = calloc(setup->nports, sizeof(float));
+    if (!setup->inputcontrols) return af_ladspa_malloc_failed(setup->myname);
+
+    setup->outputcontrolsmap = calloc(setup->nports, sizeof(int));
+    if (!setup->outputcontrolsmap) return af_ladspa_malloc_failed(setup->myname);
+
+    setup->outputcontrols = calloc(setup->nports, sizeof(float));
+    if (!setup->outputcontrols) return af_ladspa_malloc_failed(setup->myname);
+
+    /* set counts to zero */
+
+    setup->ninputs = 0;
+    setup->noutputs = 0;
+    setup->ninputcontrols = 0;
+    setup->noutputcontrols = 0;
+
+    /* check all ports, see what type it is and set variables according to
+     * what we have found
+     */
+
+    for (p=0; p<setup->nports; p++) {
+        d = pdes->PortDescriptors[p];
+
+        if (LADSPA_IS_PORT_AUDIO(d)) {
+            if (LADSPA_IS_PORT_INPUT(d)) {
+                setup->inputs[setup->ninputs] = p;
+                setup->ninputs++;
+            } else if (LADSPA_IS_PORT_OUTPUT(d)) {
+                setup->outputs[setup->noutputs] = p;
+                setup->noutputs++;
+            }
+        }
+
+        if (LADSPA_IS_PORT_CONTROL(d)) {
+            if (LADSPA_IS_PORT_INPUT(d)) {
+                setup->inputcontrolsmap[setup->ninputcontrols] = p;
+                setup->ninputcontrols++;
+                /* set control to zero. set values after reading the rest
+                 * of the suboptions and check LADSPA_?_HINT's later.
+                 */
+                setup->inputcontrols[p] = 0.0f;
+            } else if (LADSPA_IS_PORT_OUTPUT(d)); {
+                /* read and handle these too, otherwise filters that have them
+                 * will sig11
+                 */
+                setup->outputcontrolsmap[setup->noutputcontrols]=p;
+                setup->noutputcontrols++;
+                setup->outputcontrols[p] = 0.0f;
+            }
+        }
+
+    }
+
+    if (setup->ninputs == 0) {
+        mp_msg(MSGT_AF, MSGL_WARN, "%s: %s\n", setup->myname, 
+                                                MSGTR_AF_LADSPA_WarnNoInputs);
+    } else if (setup->ninputs == 1) {
+        mp_msg(MSGT_AF, MSGL_V, "%s: this is a mono effect\n", setup->myname);
+    } else if (setup->ninputs == 2) {
+        mp_msg(MSGT_AF, MSGL_V, "%s: this is a stereo effect\n", setup->myname);
+    }
+
+    if (setup->ninputs > 2) {
+        mp_msg(MSGT_AF, MSGL_ERR, "%s: %s\n", setup->myname,
+                                            MSGTR_AF_LADSPA_ErrMultiChannel);
+        return AF_ERROR;
+    }
+
+    if (setup->noutputs == 0) {
+        mp_msg(MSGT_AF, MSGL_ERR, "%s: %s\n", setup->myname,
+                                                MSGTR_AF_LADSPA_ErrNoOutputs);
+        return AF_ERROR;
+    }
+
+    if (setup->noutputs != setup->ninputs ) {
+        mp_msg(MSGT_AF, MSGL_ERR, "%s: %s\n", setup->myname,
+                                                MSGTR_AF_LADSPA_ErrInOutDiff);
+        return AF_ERROR;
+    }
+
+    mp_msg(MSGT_AF, MSGL_V, "%s: this plugin has %d input control(s)\n",
+                                        setup->myname, setup->ninputcontrols);
+
+    /* Print list of controls and its range of values it accepts */
+
+    for (i=0; i<setup->ninputcontrols; i++) {
+        p = setup->inputcontrolsmap[i];
+        hint = pdes->PortRangeHints[p];
+        mp_msg(MSGT_AF, MSGL_V, "  --- %d %s [", i, pdes->PortNames[p]);
+
+        if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor)) {
+            mp_msg(MSGT_AF, MSGL_V, "%0.2f , ", hint.LowerBound);
+        } else {
+            mp_msg(MSGT_AF, MSGL_V, "... , ");
+        }
+
+        if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor)) {
+            mp_msg(MSGT_AF, MSGL_V, "%0.2f]\n", hint.UpperBound);
+        } else {
+            mp_msg(MSGT_AF, MSGL_V, "...]\n");
+        }
+
+    }
+
+    return AF_OK;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/* This function might "slightly" look like dlopenLADSPA in the LADSPA SDK :-)
+ * But, I changed a few things, because imho it was broken. It did not support
+ * relative paths, only absolute paths that start with a /
+ * I think ../../some/dir/foobar.so is just as valid. And if one wants to call
+ * his library '...somename...so' he's crazy, but it should be allowed.
+ * So, search the path first, try plain *filename later.
+ * Also, try adding .so first! I like the recursion the SDK did, but it's
+ * better the other way around. -af ladspa=cmt:amp_stereo:0.5 is easier to type
+ * than -af ladspa=cmt.so:amp_stereo:0.5 :-))
+ */
+
+/** \brief dlopen() wrapper
+ *
+ * This is a wrapper around dlopen(). It tries various variations of the
+ * filename (with or without the addition of the .so extension) in various
+ * directories specified by the LADSPA_PATH environment variable. If all fails
+ * it tries the filename directly as an absolute path to the library.
+ * 
+ * \param filename  filename of the library to load.
+ * \param flag      see dlopen(3) for a description of the flags.
+ *
+ * \return          returns a pointer to the loaded library on success, or
+ *                  NULL if it fails to load.
+ */
+
+static void* mydlopen(const char *filename, int flag) {
+    char *buf;
+    const char *end, *start, *ladspapath;
+    int endsinso, needslash;
+    size_t filenamelen;
+    void *result = NULL;
+
+#   ifdef WIN32         /* for windows there's only absolute path support.
+                         * if you have a windows machine, feel free to fix
+                         * this. (path separator, shared objects extension,
+                         * et cetera).
+                         */
+        mp_msg(MSGT_AF, MSGL_V, "\ton windows, only absolute pathnames "
+                "are supported\n");
+        mp_msg(MSGT_AF, MSGL_V, "\ttrying %s\n", filename);
+        return dlopen(filename, flag);
+#   endif
+
+    filenamelen = strlen(filename);
+
+    endsinso = 0;
+    if (filenamelen > 3)
+        endsinso = (strcmp(filename+filenamelen-3, ".so") == 0);
+    if (!endsinso) {
+        buf=malloc(filenamelen+4);
+        strcpy(buf, filename);
+        strcat(buf, ".so");
+        result=mydlopen(buf, flag);
+        free(buf);
+    }
+
+    if (result)
+        return result;
+
+    ladspapath=getenv("LADSPA_PATH");
+
+    if (ladspapath) {
+
+        start=ladspapath;
+        while (*start != '\0') {
+            end=start;
+            while ( (*end != ':') && (*end != '\0') )
+                end++;
+
+            buf=malloc(filenamelen + 2 + (end-start) );
+            if (end > start)
+                strncpy(buf, start, end-start);
+            needslash=0;
+            if (end > start)
+                if (*(end-1) != '/') {
+                    needslash = 1;
+                    buf[end-start] = '/';
+                }
+            strcpy(buf+needslash+(end-start), filename);
+
+            mp_msg(MSGT_AF, MSGL_V, "\ttrying %s\n", buf);
+            result=dlopen(buf, flag);
+
+            free(buf);
+            if (result)
+                return result;
+
+            start = end;
+            if (*start == ':')
+                start++;
+        } /* end while there's still more in the path */
+    } /* end if there's a ladspapath */
+
+    /* last resort, just open it again, so the dlerror() message is correct */
+    mp_msg(MSGT_AF, MSGL_V, "\ttrying %s\n", filename);
+    return dlopen(filename,flag);
+}
+
+/* ------------------------------------------------------------------------- */
+
+/** \brief Load a LADSPA Plugin
+ *
+ * This function loads the LADSPA plugin specified by the file and label
+ * that are present in the setup variable. First, it loads the library.
+ * If it fails, it returns AF_ERROR. If not, it continues to look for the
+ * specified label. If it finds it, it sets the plugin_descriptor inside
+ * setup and returns AF_OK. If it doesn't, it returns AF_ERROR. Special case
+ * is a label called 'help'. In that case, it prints a list of all available
+ * labels (filters) in the library specified by file.
+ *
+ * \param setup     Current setup of the filter. Contains filename and label.
+ *
+ * \return  Either AF_ERROR or AF_OK, depending on the success of the operation.
+ */
+
+static int af_ladspa_load_plugin(af_ladspa_t *setup) {
+    const LADSPA_Descriptor *ladspa_descriptor;
+    LADSPA_Descriptor_Function descriptor_function;
+    int i;
+
+    /* load library */
+    mp_msg(MSGT_AF, MSGL_V, "%s: loading ladspa plugin library %s\n",
+                                                setup->myname, setup->file);
+
+    setup->libhandle = mydlopen(setup->file, RTLD_NOW);
+
+    if (!setup->libhandle) {
+        mp_msg(MSGT_AF, MSGL_ERR, "%s: %s %s\n\t%s\n", setup->myname,
+                    MSGTR_AF_LADSPA_ErrFailedToLoad, setup->file, dlerror() );
+        return AF_ERROR;
+    }
+
+    mp_msg(MSGT_AF, MSGL_V, "%s: library found.\n", setup->myname);
+
+    /* find descriptor function */
+    dlerror();
+    descriptor_function = (LADSPA_Descriptor_Function) dlsym (setup->libhandle,
+                                                        "ladspa_descriptor");
+
+    if (!descriptor_function) {
+        mp_msg(MSGT_AF, MSGL_ERR, "%s: %s\n\t%s\n", setup->myname,
+                                MSGTR_AF_LADSPA_ErrNoDescriptor, dlerror());
+        return AF_ERROR;
+    }
+
+    /* if label == help, list all labels in library and exit */
+
+    if (strcmp(setup->label, "help") == 0) {
+        mp_msg(MSGT_AF, MSGL_INFO, "%s: %s %s:\n", setup->myname, 
+                MSGTR_AF_LADSPA_AvailableLabels, setup->file);
+        for (i=0; ; i++) {
+            ladspa_descriptor = descriptor_function(i);
+            if (ladspa_descriptor == NULL) {
+                return AF_ERROR;
+            }
+            mp_msg(MSGT_AF, MSGL_INFO, "  %-16s - %s (%lu)\n",
+                    ladspa_descriptor->Label,
+                    ladspa_descriptor->Name,
+                    ladspa_descriptor->UniqueID);
+        }
+    }
+
+    mp_msg(MSGT_AF, MSGL_V, "%s: looking for label\n", setup->myname);
+
+    /* find label in library */
+    for (i=0; ; i++) {
+        ladspa_descriptor = descriptor_function(i);
+        if (ladspa_descriptor == NULL) {
+            mp_msg(MSGT_AF, MSGL_ERR, "%s: %s\n", setup->myname,
+                                            MSGTR_AF_LADSPA_ErrLabelNotFound);
+            return AF_ERROR;
+        }
+        if (strcmp(ladspa_descriptor->Label, setup->label) == 0) {
+            setup->plugin_descriptor = ladspa_descriptor;
+            mp_msg(MSGT_AF, MSGL_V, "%s: %s found\n", setup->myname,
+                                                                setup->label);
+            return AF_OK;
+        }
+    }
+
+    return AF_OK;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/** \brief Print a malloc() failed error message.
+ *
+ * Generic function which can be called if a call to malloc(), calloc(),
+ * strdup(), et cetera, failed. It prints a message to the console and
+ * returns AF_ERROR.
+ *
+ * \return  AF_ERROR
+ */
+
+static int af_ladspa_malloc_failed(char *myname) {
+    mp_msg(MSGT_AF, MSGL_ERR, "%s: %s\n", myname, MSGTR_MemAllocFailed);
+    return AF_ERROR;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/** \brief Controls the filter.
+ *
+ * Control the behaviour of the filter.
+ *
+ * Commands:
+ * CONTROL_REINIT   Sets the af structure with proper values for number
+ *                  of channels, rate, format, et cetera.
+ * CONTROL_COMMAND_LINE     Parses the suboptions given to this filter
+ *                          through arg. It first parses the filename and
+ *                          the label. After that, it loads the filter
+ *                          and finds out its proprties. Then in continues
+ *                          parsing the controls given on the commandline,
+ *                          if any are needed.
+ *
+ * \param af    Audio filter instance
+ * \param cmd   The command to execute
+ * \param arg   Arguments to the command
+ *
+ * \return      Either AF_ERROR or AF_OK, depending on the succes of the
+ *              operation.
+ */
+
+static int control(struct af_instance_s *af, int cmd, void *arg) {
+    af_ladspa_t *setup = (af_ladspa_t*) af->setup;
+    int i, r;
+    float val;
+
+    switch(cmd) {
+    case AF_CONTROL_REINIT:
+        mp_msg(MSGT_AFILTER, MSGL_V, "%s: (re)init\n", setup->myname);
+
+        if (!arg) return AF_ERROR;
+
+        /* for now, only accept 16 bit signed int */
+
+        af->data->rate   = ((af_data_t*)arg)->rate;
+        af->data->nch    = ((af_data_t*)arg)->nch;
+        af->data->format = AF_FORMAT_SI | AF_FORMAT_NE;
+        af->data->bps    = 2;
+
+        /* arg->len is not set here yet, so init of buffers and connecting the
+         * filter, has to be done in play() :-/
+         */
+
+        return af_test_output(af, (af_data_t*)arg);
+    case AF_CONTROL_COMMAND_LINE: {
+        char *buf;
+
+        mp_msg(MSGT_AFILTER, MSGL_V, "%s: parse suboptions\n", setup->myname);
+
+        /* suboption parser here!
+         * format is (ladspa=)file:label:controls....
+         */
+
+        if (!arg) {
+            mp_msg(MSGT_AF, MSGL_ERR, "%s: %s\n", setup->myname,
+                                            MSGTR_AF_LADSPA_ErrNoSuboptions);
+            return AF_ERROR;
+        }
+
+        buf = malloc(strlen(arg)+1);
+        if (!buf) return af_ladspa_malloc_failed(setup->myname);
+
+        /* file... */
+        buf[0] = '\0';
+        sscanf(arg, "%[^:]", buf);
+        if (buf[0] == '\0') {
+            mp_msg(MSGT_AF, MSGL_ERR, "%s: %s\n", setup->myname,
+                                                MSGTR_AF_LADSPA_ErrNoLibFile);
+            free(buf);
+            return AF_ERROR;
+        }
+        arg += strlen(buf);
+        setup->file = strdup(buf);
+        if (!setup->file) return af_ladspa_malloc_failed(setup->myname);
+        mp_msg(MSGT_AF, MSGL_V, "%s: file --> %s\n", setup->myname,
+                                                        setup->file);
+        if (*(char*)arg != '\0') arg++; /* read ':' */
+
+        /* label... */
+        buf[0] = '\0';
+        sscanf(arg, "%[^:]", buf);
+        if (buf[0] == '\0') {
+            mp_msg(MSGT_AF, MSGL_ERR, "%s: %s\n", setup->myname,
+                                                MSGTR_AF_LADSPA_ErrNoLabel);
+            free(buf);
+            return AF_ERROR;
+        }
+        arg += strlen(buf);
+        setup->label = strdup(buf);
+        if (!setup->label) return af_ladspa_malloc_failed(setup->myname);
+        mp_msg(MSGT_AF, MSGL_V, "%s: label --> %s\n", setup->myname,
+                                                                setup->label);
+/*        if (*(char*)arg != '0') arg++; */ /* read ':' */
+ 
+        free(buf); /* no longer needed */
+
+        /* set new setup->myname */
+
+        if(setup->myname) free(setup->myname);
+        setup->myname = calloc(strlen(af_info_ladspa.name)+strlen(setup->file)+
+                                                    strlen(setup->label)+6, 1);
+        snprintf(setup->myname, strlen(af_info_ladspa.name)+
+                strlen(setup->file)+strlen(setup->label)+6, "%s: (%s:%s)",
+                            af_info_ladspa.name, setup->file, setup->label);
+
+        /* load plugin :) */
+
+        if ( af_ladspa_load_plugin(setup) != AF_OK )
+            return AF_ERROR;
+
+        /* see what inputs, outputs and controls this plugin has */
+        if ( af_ladspa_parse_plugin(setup) != AF_OK )
+            return AF_ERROR;
+
+        /* ninputcontrols is set by now, read control values from arg */
+
+        for(i=0; i<setup->ninputcontrols; i++) {
+            if (!arg || (*(char*)arg != ':') ) {
+                mp_msg(MSGT_AF, MSGL_ERR, "%s: %s\n", setup->myname,
+                                        MSGTR_AF_LADSPA_ErrNotEnoughControls);
+                return AF_ERROR;
+            }
+            arg++;
+            r = sscanf(arg, "%f", &val);
+            if (r!=1) {
+                mp_msg(MSGT_AF, MSGL_ERR, "%s: %s\n", setup->myname,
+                                        MSGTR_AF_LADSPA_ErrNotEnoughControls);
+                return AF_ERROR;
+            }
+            setup->inputcontrols[setup->inputcontrolsmap[i]] = val;
+            arg = strchr(arg, ':');
+        }
+
+        mp_msg(MSGT_AF, MSGL_V, "%s: input controls: ", setup->myname);
+        for(i=0; i<setup->ninputcontrols; i++) {
+            mp_msg(MSGT_AF, MSGL_V, "%0.4f ",
+                            setup->inputcontrols[setup->inputcontrolsmap[i]]);
+        }
+        mp_msg(MSGT_AF, MSGL_V, "\n");
+
+        /* check boundaries of inputcontrols */
+
+        mp_msg(MSGT_AF, MSGL_V, "%s: checking boundaries of input controls\n",
+                                                                setup->myname);
+        for(i=0; i<setup->ninputcontrols; i++) {
+            int p = setup->inputcontrolsmap[i];
+            LADSPA_PortRangeHint hint =
+                                setup->plugin_descriptor->PortRangeHints[p];
+            val = setup->inputcontrols[p];
+
+            if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor) &&
+                    val < hint.LowerBound) {
+                mp_msg(MSGT_AF, MSGL_ERR, MSGTR_AF_LADSPA_ErrControlBelow,
+                                            setup->myname, i, hint.LowerBound);
+                return AF_ERROR;
+            }
+            if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor) &&
+                    val > hint.UpperBound) {
+                mp_msg(MSGT_AF, MSGL_ERR, MSGTR_AF_LADSPA_ErrControlAbove,
+                                            setup->myname, i, hint.UpperBound);
+                return AF_ERROR;
+            }
+        }
+        mp_msg(MSGT_AF, MSGL_V, "%s: all controls have sane values\n",
+                                                                setup->myname);
+
+        /* All is well! */
+        setup->status = AF_OK;
+
+        return AF_OK; }
+    }
+
+    return AF_UNKNOWN;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/** \brief Uninitialise the LADSPA Plugin Loader filter.
+ *
+ * This function deactivates the plugin(s), cleans up, frees all allocated
+ * memory and exits.
+ *
+ * \return  No return value.
+ */
+
+static void uninit(struct af_instance_s *af) {
+    int i;
+
+    if (af->data)
+        free(af->data);
+    if (af->setup) {
+        af_ladspa_t *setup = (af_ladspa_t*) af->setup;
+        const LADSPA_Descriptor *pdes = setup->plugin_descriptor;
+
+        if (setup->myname) {
+            mp_msg(MSGT_AF, MSGL_V, "%s: cleaning up\n", setup->myname);
+            free(setup->myname);
+        }
+
+        if (setup->chhandles) {
+            for(i=0; i<setup->nch; i++) {
+                if ( (setup->ninputs == 2) && (i & 1) ) { /* stereo effect */
+                    i++;
+                    continue;
+                }
+                if (pdes->deactivate) pdes->deactivate(setup->chhandles[i]);
+                if (pdes->cleanup) pdes->cleanup(setup->chhandles[i]);
+            }
+            free(setup->chhandles);
+        }
+
+        if (setup->file)
+            free(setup->file);
+        if (setup->label)
+            free(setup->label);
+        if (setup->inputcontrolsmap)
+            free(setup->inputcontrolsmap);
+        if (setup->inputcontrols)
+            free(setup->inputcontrols);
+        if (setup->outputcontrolsmap)
+            free(setup->outputcontrolsmap);
+        if (setup->outputcontrols)
+            free(setup->outputcontrols);
+        if (setup->inputs)
+            free(setup->inputs);
+        if (setup->outputs)
+            free(setup->outputs);
+
+        if (setup->inbufs) {
+            for(i=0; i<setup->nch; i++) {
+                if (setup->inbufs[i])
+                    free(setup->inbufs[i]);
+            }
+            free(setup->inbufs);
+        }
+
+        if (setup->outbufs) {
+            for(i=0; i<setup->nch; i++) {
+                if (setup->outbufs[i])
+                    free(setup->outbufs[i]);
+            }
+            free(setup->outbufs);
+        }
+
+        if (setup->libhandle)
+            dlclose(setup->libhandle);
+
+        free(setup);
+        setup = NULL;
+    }
+}
+
+/* ------------------------------------------------------------------------- */
+
+/** \brief Process chunk of audio data through the selected LADSPA Plugin.
+ * 
+ * \param af    Pointer to audio filter instance
+ * \param data  Pointer to chunk of audio data
+ *
+ * \return      Either AF_ERROR or AF_OK
+ */
+
+static af_data_t* play(struct af_instance_s *af, af_data_t *data) {
+    af_ladspa_t *setup = af->setup;
+    const LADSPA_Descriptor *pdes = setup->plugin_descriptor;
+    int16_t *audio = (int16_t*)data->audio;
+    int nsamples = data->len/2; /* /2 because it's int16_t */
+    int nch = data->nch;
+    int rate = data->rate;
+    int i, p; 
+    float v;
+
+    if (setup->status !=AF_OK)
+        return data;
+
+    /* See if it's the first call. If so, setup inbufs/outbufs, instantiate
+     * plugin, connect ports and activate plugin
+     */
+
+    /* 2004-12-07: Also check if the buffersize has to be changed!
+     *             data->len is not constant per se! re-init buffers.
+     */
+
+    if ( (setup->bufsize != nsamples/nch) || (setup->nch != nch) ) {
+
+        /* if setup->nch==0, it's the first call, if not, something has
+         * changed and all previous mallocs have to be freed
+         */
+
+        if (setup->nch != 0) {
+            mp_msg(MSGT_AF, MSGL_DBG2, "%s: bufsize change; free old buffer\n",
+                                                                setup->myname);
+
+            if(setup->inbufs) {
+                for(i=0; i<setup->nch; i++) {
+                    if(setup->inbufs[i])
+                        free(setup->inbufs[i]);
+                }
+                free(setup->inbufs);
+            }
+            if(setup->outbufs) {
+                for(i=0; i<setup->nch; i++) {
+                    if(setup->outbufs[i])
+                        free(setup->outbufs[i]);
+                }
+                free(setup->outbufs);
+            }
+        } /* everything is freed */
+
+        setup->bufsize = nsamples/nch;
+        setup->nch = nch;
+
+        setup->inbufs = calloc(nch, sizeof(float*));
+        setup->outbufs = calloc(nch, sizeof(float*));
+
+        mp_msg(MSGT_AF, MSGL_DBG2, "%s: bufsize = %d\n",
+                                        setup->myname, setup->bufsize);
+
+        for(i=0; i<nch; i++) {
+            setup->inbufs[i] = calloc(setup->bufsize, sizeof(float));
+            setup->outbufs[i] = calloc(setup->bufsize, sizeof(float));
+        }
+
+        /* only on the first call, there are no handles. */
+
+        if (!setup->chhandles) {
+            setup->chhandles = calloc(nch, sizeof(LADSPA_Handle));
+
+            /* create handles
+             * for stereo effects, create one handle for two channels
+             */
+
+            for(i=0; i<nch; i++) {
+
+                if ( (setup->ninputs == 2) && (i & 1) ) { /* stereo effect */
+                    /* copy the handle from previous channel */
+                    setup->chhandles[i] = setup->chhandles[i-1];
+                    continue;
+                }
+
+                setup->chhandles[i] = pdes->instantiate(pdes, rate);
+            }
+        }
+
+        /* connect input/output ports for each channel/filter instance
+         *
+         * always (re)connect ports
+         */
+
+        for(i=0; i<nch; i++) {
+            pdes->connect_port(setup->chhandles[i],
+                               setup->inputs[ (setup->ninputs==2) ? i&1 : 0 ],
+                               setup->inbufs[i]);
+            pdes->connect_port(setup->chhandles[i],
+                               setup->outputs[ (setup->ninputs==2) ? i&1 : 0 ],
+                               setup->outbufs[i]);
+
+            /* connect (input) controls */
+
+            for (p=0; p<setup->nports; p++) {
+                LADSPA_PortDescriptor d = pdes->PortDescriptors[p];
+                if (LADSPA_IS_PORT_CONTROL(d)) {
+                    if (LADSPA_IS_PORT_INPUT(d)) {
+                        pdes->connect_port(setup->chhandles[i], p,
+                                                &(setup->inputcontrols[p]) );
+                    } else {
+                        pdes->connect_port(setup->chhandles[i], p,
+                                                &(setup->outputcontrols[p]) );
+                    }
+                }
+            }
+
+            /* Activate filter (if it isn't already :) ) */
+
+            if ( (pdes->activate) && (setup->activated == 0) ) {
+                pdes->activate(setup->chhandles[i]);
+                setup->activated = 1;
+            }
+
+        } /* All channels/filters done! except for... */
+
+        /* Stereo effect with one channel left. Use same buffer for left
+         * and right. connect it to the second port.
+         */
+
+        if( (setup->ninputs == 2) && (i&1) && (i >= 1) ) { 
+            pdes->connect_port(setup->chhandles[i-1],
+                               setup->inputs[ (setup->ninputs==2) ? i&1 : 0 ],
+                               setup->inbufs[i-1]);
+            pdes->connect_port(setup->chhandles[i-1],
+                               setup->outputs[ (setup->ninputs==2) ? i&1 : 0 ],
+                               setup->outbufs[i-1]);
+        } /* done! */
+
+    } /* setup for first call/change of bufsize is done.
+       * normal playing routine follows...
+       */
+
+    /* Right now, I use a separate input and output buffer.
+     * I could change this to in-place processing (inbuf==outbuf), but some
+     * ladspa filters are broken and are not able to handle that. This seems
+     * fast enough, so unless somebody complains, it stays this way :)
+     */
+
+    /* Fill inbufs */
+
+    for (p=0; p<setup->bufsize; p++) {
+        for (i=0; i<nch; i++) {
+            setup->inbufs[i][p] = ( (float) audio[p*nch + i] ) / 32768.0f;
+        }
+    }
+
+    /* Run filter(s) */
+
+    for (i=0; i<nch; i++) {
+        pdes->run(setup->chhandles[i], setup->bufsize);
+        if (setup->ninputs==2) // stereo effect just ran
+            i++;
+    }
+
+    /* Extract outbufs, hard clipping in case the filter exceeded [-1.0,1.0] */
+
+    for (p=0; p<setup->bufsize; p++) {
+        for (i=0; i<nch; i++) {
+            v = setup->outbufs[i][p];
+            v *= 32768.0f;
+            v = (v > 32767.0f ? 32767.0f : v);
+            v = (v < -32768.0f ? -32768.0f : v);
+            audio[p*nch + i] = (int16_t) v;
+        }
+    }
+
+    /* done */
+
+    return data;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/** \brief Open LADSPA Plugin Loader Filter
+ *
+ * \param af    Audio Filter instance
+ *
+ * \return      Either AF_ERROR or AF_OK
+ */
+
+static int open(af_instance_t *af) {
+
+    af->control=control;
+    af->uninit=uninit;
+    af->play=play;
+    af->mul.n=1;
+    af->mul.d=1;
+
+    af->data = calloc(1, sizeof(af_data_t));
+    if (af->data == NULL)
+        return af_ladspa_malloc_failed((char*)af_info_ladspa.name);
+
+    af->setup = calloc(1, sizeof(af_ladspa_t));
+    if (af->setup == NULL) {
+        free(af->data);
+        af->data=NULL;
+        return af_ladspa_malloc_failed((char*)af_info_ladspa.name);
+    }
+
+    ((af_ladspa_t*)af->setup)->status = AF_ERROR; /* will be set to AF_OK if
+                                                   * all went OK and play()
+                                                   * should proceed.
+                                                   */
+
+    ((af_ladspa_t*)af->setup)->myname = strdup(af_info_ladspa.name);
+    if (!((af_ladspa_t*)af->setup)->myname)
+        return af_ladspa_malloc_failed((char*)af_info_ladspa.name);
+
+    return AF_OK;
+}
+
+/* ------------------------------------------------------------------------- */
+
+#undef MSGT_AF
+