Re: new module module-plugin-sink

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 10.05.19 19:27, Tanu Kaskinen wrote:
On Sat, 2019-05-04 at 22:41 +0200, Georg Chini wrote:
I don't see the need for copying the channel position and error enums.
We can #include the relevant headers.

I thought the point was to create a header file that allows development
of filters completely independent from pulseaudio, that is you can compile
the code without having PA installed. If that was not the intention, we can
include the headers. Can we then also link with libpulse?



/* Function called when the filter detects a condition to unload the filter. */
typedef void (*Kill_Filter_Function)(void *module);
What's the use case for for filter self-destruction?

A filter might detect that it is no longer appropriate based on the
sink/port information it receives. Then it can either disable itself
internally or just kill itself, so that PA will take over processing.



I think we should use the normal PA naming conventions, so this would
be named pa_filter_plugin_kill_cb_t.

Why should we follow PA naming conventions? Again, I thought
it should be rather independent from PA and authors of filters
do not need to know PA internals. That's one of the reasons I
dropped the PA_ prefix for channel map and error values.
If you prefer, I can nevertheless change back to PA conventions.



/* Filter plugin structure */
typedef struct filter_plugin {
     const char *name;                         /* Name of the filter. Used to identify the filter. */
Is this expected to be globally unique? Are there limitations on length
or accepted characters? Would there be problems with using the .so file
name as the name? I expect the .so file name to be needed for
identification anyway, and it's not clear to me what benefit another
layer of naming brings.

It would be nice to have this globally unique, but I think that is difficult
to ensure. The concept of loading filters follows the LADSPA scheme,
so one file can contain multiple filters. See comment to the
get_Filter_Info() function.



     const char *desc_head;                    /* Leading part of description string used for the
                                                * sink and sink input description in PA. */
Can you give an example how this is used? What about sources, maybe
we'll one day have module-plugin-source too?
The sources we currently have are quite different and not easy
to consolidate. So I decided (for the moment) to leave it at that.

Is this the same thing that is returned as the filter description in
the parameter-get-description message? If so, I think "description"
would be a better name for this field than "desc_head".

No, this has nothing to do with the parameter description. This
is just a bit of text that is used in the description property of
sink and sink input. For the virtual sink it would just be
"Virtual Sink" or "LADSPA Sink" for the ladspa sink. There is
surely a better name for it ...



     const char *filter_type;                  /* Name for the type of filter, used as suffix for
                                                * the sink name if the name is derived from the
                                                * master sink. */
I believe using a prefix is better than suffix. A tunnel sink has
prefix "tunnel.", so if there's a sink named "tunnel.foo.eq", it's
ambiguous whether that's a tunnel sink connected to remote "foo.eq"
sink or a local eq sink using "tunnel.foo" as master. "eq.tunnel.foo"
doesn't have this problem.

The current virtual sinks all use a suffix. Are you OK with changing
that for all virtual sinks?



     unsigned input_channels;                  /* Number of audio input channels, 0 if variable */
     unsigned output_channels;                 /* Number of audio output channels, 0 if variable */
How are these used? You wanted to avoid deinterleaving, so I guess mono
filters are expected to support variable channels. What kind of filters
are going to set fixed channels? Is a filter expected to support all
possible channel counts if it supports more than one channel count?

Either the filter provides a fixed number of channels (for example
2 output and 6 input channels for a virtual surround filter) or the
number of channels can be set freely (based on master sink and/or
provided channel maps). There may be filter specific conditions like
number of in/out channels being equal, but this must be checked
by the filter at initialization time.



     size_t max_chunk_size;                    /* Maximum chunk size in bytes that the filter will
                                                * accept, 0 for default */
Shouldn't this be in frames rather than in bytes, since the other
fields use frames? And what's the difference between "chunk" and
"block"? If there's none, then let's call it a "block" always.

What's the default chunk size? Does 0 mean that the chunk size is
allowed to change between process_chunk() calls?

Is this even necessary? LADSPA doesn't define a maximum block size.

This is supposed to be a memory limit like 32kB, regardless of
the number of channels, that's why it is not in frames. I am
not sure if it is necessary, it was something that was set by
one of the existing filters. Default (and maximum) is
pa_frame_align(pa_mempool_block_size_max(s->core->mempool), &s->sample_spec)



     size_t fixed_block_size;                  /* Block size in frames for fixed block size filters,
                                                * 0 if block size is variable */
I guess this is added here, because module-echo-cancel uses a fixed
block size. LADSPA plugins don't seem to care about the block size.

With module-echo-cancel the block size is configurable, but this API
doesn't allow configuring the block size. My suggestion would be to
replace this field with a block size initialization parameter. If the
plugin doesn't have that initialization parameter, then it's assumed to
accept variable block sizes. (More on initialization parameters later.)

This is for the case that the filter requires a specific block size.
Then it will specify it in this value. If the filter does not provide
a block size, it is assumed, that it can handle variable block sizes.
What additional API would be needed?



     int max_rewind;                           /* Maximum number of frames that the sink can rewind.
                                                * 0 means use value from master sink, -1 disables
                                                * rewinding. Unless the filter implements rewinding
                                                * the value should be set to -1. */
"0 means use value from master sink" - wouldn't it be better to define
0 simply as "unlimited"? That is, the plugin doesn't impose any
limitation on the maximum rewind amount; the hardware of course defines
some maximum.

I meant unlimited.



     /* Functions defined by the filter */

     /* Initializes a new filter instance and returns a handle to it. Passes the number of
      * input and output channels, the corresponding channel positions and the sample rate
      * to the filter. kill_filter can be called by the filter if it detects a condition
      * that makes it necessary to remove the filter. kill_filter must be called with
      * the module pointer as argument. */
     void *(*create_filter)(unsigned input_channels, int *input_map,
                            unsigned output_channels, int *output_map,
                            Kill_Filter_Function kill_filter, void *module, unsigned sample_rate);
There doesn't seem to be any concept of initialization parameters,
except the ones that are explicitly listed here in the create_filter()
arguments. I thought the main motivation for this exercise was to
facilitate the new eq algorithm that supports different amounts of
frequency bands. How can the eq bands be configured? Are you still
suggesting that the number of bands should be a regular parameter?

Yes, this can easily be coded in the meta data. Naturally, a parameter
which changes the total number of parameters cannot be set alone,
but only with parameter-set-all.



My suggestion would be to have an initialization parameter API to do
the following:

  - query the initialization parameters that the plugin supports (e.g.
block size, sample rate, eq bands)
  - query the default values for the initialization parameters
  - set the initialization parameters

Each filter should come with a reasonable set of default parameters
in place, because, contrary to LADSPA, it is the filters responsibility to
handle and provide parameter structures. These default values can
be queried using the meta data and changed as needed. All data
needed by PA for the initialization should be in the filter_plugin structure
that the filter provides, and the things that the filter needs to know
should be passed in the create() call. Therefore I cannot see the point
of introducing a special API. Can you provide a use case that can not
be handled by the current approach?


The metadata for the initialization parameters should probably be the
same as for runtime parameters: identifier, description, type, default
value, range. Some initialization parameters would have their
identifiers and semantics defined by the specification (at least block
size and sample rate).
See above.

I'm not sure if it makes sense to make the channel configuration part
of the initialization parameters. If the initialization parameter API
should only cover parameters that the user can change, then the sample
rate shouldn't be an initialization parameter either.
Well, the filter needs to know the sample rate, so it must be passed
somewhere. Sample rate and channel information are critical for
the function of the filter, so I think they must be passed at creation
time.


I think the void pointers should be replaced with typedefs, so that
it's clear what's what. Void pointers are used for several things in
this API.
OK.

The "module" parameter could be renamed to kill_cb_data to not expose
unnecessary implementation details, and to make its purpose clearer.
OK, but again I do not see the need to stick to PA naming conventions.

     /* Deletes filter instance. */
     void (*delete_filter)(void *filter_handle);

     /* Activates filter. Returns 0 on success and a negative errror code on failure.
      * May be NULL. */
     int (*activate_filter)(void *filter_handle);

     /* Deactivates filter. May be NULL. */
     void (*deactivate_filter)(void *filter_handle);

     /* Callback to process a chunk of data by the filter. May be NULL */
     void (*process_chunk)(float *src, float *dst, unsigned count, void *filter_handle);

     /* Callback to retrieve additional latency caused by the filter. May be NULL */
     uint64_t (*get_extra_latency)(void *filter_handle);

     /* Callback to rewind the filter when pulseaudio requests it. May be NULL.
      * Amount indicates the number of bytes to roll back. The filter state must
      * not be reset, but seamlessly restored to the specified point in the past
      * (which is the filter's responsibility to keep).
      * If it is not possible to do so, the best option is to disable rewinding
      * completely and limit the latency. */
     void (*rewind_filter)(size_t amount, void *filter_handle);

     /* If not NULL, this function is called whenever the active port of a sink or
      * the sink itself changes. It is used to communicate the currently active port
      * and sink to the filter. */
     void (*output_changed)(const char *sink_name, const char *port_name, void *filter_handle);
What is the filter supposed to do with this information? I recall from
the earlier discussion that the filter could disable itself, if the
sink name and the port name look somehow bad, but there doesn't seem to
be any mechanism for telling the filter which sinks and ports are bad.
I don't think this logic belongs in the filter anyway, the policy code
for enabling/disabling filters automatically should be implemented
separately.

See Alexanders response to that topic. I think, that the sink information
is only there to detect if the filter has moved, while the port information
may be something that a filter can use. It might for example distinguish
between playing on speakers or headphones. The sink/port information
might also be something that a GUI wants to query.



     /* If set, this function is called in thread context when an update of the
For clarity, "thread context" -> "the IO thread context".

The call context should be documented for all functions.

      * filter parameters is requested, may be NULL. The function must replace
      * the currently used parameter structure by the new structure and return
      * a pointer to the old structure so that it can be freed in the main thread
      * using parameter_free(). If the old structure can be re-used, the function
      * may return NULL. */
     void *(*update_filter_parameters)(void *parameters, void *filter_handle);

     /* Frees a parameter structure. May only be NULL, if update_filter_parameters()
      * is also NULL or if update_filter_parameters() always returns NULL. */
     void (*parameter_free)(void *parameters);
I'd rename this to "parameters_free" because the function handles
parameter sets, not single parameters. Even better would be
"free_parameters", because that's better English. I guess you chose to
put "free" last, because most of the time freeing functions have "free"
at the end of the function name, but that's because therey're part of a
namespace (like "pa_idxset_free" is a part of namespace "pa_idxset_".)

Maybe we should call them "filter parameters" instead of just
"parameters" (you did this with "update_filter_parameters" too, rather
than calling it just "update_parameters"). If we add the initialization
parameter concept, then there will be two kinds of parameters.

     /* The following functions can be provided to set and get parameters. The parameter
      * structure is defined by the filter and should contain all parameters that are
      * configurable by the user. The host code makes no assumption on the contents
      * of the structure but only passes references. The host implements a message
      * handler which supports the following messages that use the functions below:
      * parameter-get - Retrieve a single parameter.
      * parameter-set - Set a single parameter.
      * parameter-get-all - Retrieve all parameters as a list in message format.
      * parameter-set-all - Set all parameters simultaneously.
      * parameter-get-description - Returns a filter description and a list that describes
      *                             all parameters. Example of the list element format:
      *                             {{Identifier}{Type}{default}{min}{max}}
      * The last message can be used to get information about the filter or to implement
      * a filter control GUI that is independent of the filter type.
      * If filter_message_handler() is defined, all other messages are passed on to the
      * filter. The get functions are expected to return a string in the message handler
      * format while the set functions receive plain strings. On failure, all get/set
      * functions will return NULL. */
I think parameter_get() should return a plain string.

I don't like that parameter_get_all() is required to wrap the parameter
values in the message API format, but that's probably the pragmatic
choice since returning an array would probably only cause extra
overhead when the array needs to be allocated, populated, and finally
converted to the message API format anyway.

I don't like it either and you are right, this has been a pragmatic
choice. I decided to wrap the response to parameter-get in curly
braces for consistency, so that all the get messages return a
string in message format (and the caller can simply pass it on
to the messaging functions) while all set messages receive plain
strings (and the filter needs not parse the messaging format).



One possibility would be to handle the parameter-get-all message in the
host, which would call parameter_get() in a loop. The filter would have
to somehow provide the control names in order for this to be possible.
If we choose that path, then we could consider implementing the
parameter-get-description message handler in the host too. The filter
would provide the necessary metadata in some appropriate C structures.
I don't expect this to reduce complexity, though, it's just a way to
avoid any message API details in the filter API.

I tried to simplify it for the filter side so that it does not have to
parse message strings. I think we should provide some utility
functions for constructing message strings, similar to what PA
has. I do not like calling parameter-set (or parameter-get) in a loop,
this would mean that the parameters change one by one which
might be audible, especially if you think about changing parameters
that influence the size of the parameter set.
If we want to avoid that the filter has to construct strings, I would
rather change the interface to passing arrays, though that means
allocating and de-allocating a lot of strings.



I think "filter" would be a better prefix for the message names than
"parameter". "parameter-get-description" in particular doesn't seem
very good to me, because the message returns some information that is
not related to parameters.

Since filters have the desc_head field (which may get renamed to
"description"), "filter-get-description" sounds like it only returns
that field. Since it actually returns the full metadata, it should be
named "filter-get-metadata".
Agreed.

I'd also rename the get/set functions:

get_(filter_)parameter
set_(filter_)parameter
get_all_(filter_)parameters
set_all_(filter_)parameters
Agreed.

     /* Get the value of the parameter described by identifier. The value shall be returned
      * as a string enclosed in double curly braces. The identifier may be any string that
      * the filter recognizes like a name or index, depending on the implementation of this
      * function. */
     char *(*parameter_get)(const char *identifier, void *filter_handle);

     /* Sets the value of the parameter described by identifier. The value is expected as
      * a string. The identifier may be any string that the filter recognizes like a name
      * or index. Returns a parameter structure filled with all current parameter values,
      * reflecting the updated parameter, so that the structure can be passed to
      * update_filter_parameters(). update_filter_parameters() will replace the current
      * parameter set with the new structure. */
     void *(*parameter_set)(const char *identifier, const char *value, void *filter_handle);

     /* Returns a list of the values of all filter parameters. Each parameter must be enclosed
      * in curly braces and there must be braces around the whole list. */
     char *(*parameter_get_all)(void *filter_handle);

     /* Expects an array of all filter parameter values as strings and returns a parameter
      * structure that can be passed to update_filter_parameters(). See set_parameter().
      * If all_params is NULL, the function should return a default set of parameters. */
     void *(*parameter_set_all)(const char **all_params, int param_count, void *filter_handle);

     /* Returns a parameter description string as described above. */
     char *(*parameter_get_description)(void *filter_handle);

     /* Handler for additional messages. */
     int (*filter_message_handler)(const char *message, char *message_parameters, char **response, void *filter_handle);
Do you have some ideas for how this would get used?

This would be used to get some information from the filter or to change
things that cannot be changed via parameters. I can't really say what it
would be used for, but it comes at no additional cost, so I thought I should
include it. At least it is nice for testing purposes.



} filter_plugin;
It might be a good idea to add an API version field to the struct, and
maybe another field for indicating the minimum API version that the
plugin requires from the host (ideally all plugins would support all
past API versions, though).

The API version would be incremented when new fields are added to the
struct or something else is changed in the spec.

The structure is provided by the filter, not by PA, so it could
only be a field that indicates which API version is required.



I would leave all the rewind stuff out, and if someone really wants to
have rewinding support in their plugin, that can be added in v2.
Meanwhile I have it implemented correctly which was really not
easy to achieve, so I am very reluctant to leave this part out.
It is already part of !88, so there is no additional cost to add it
here. The standard approach will be to disable rewinding anyway.


/* Datatype corresponding to the get_Filter_Info() function. */
The get_Filter_Info() function is not explained anywhere.

typedef const struct filter_plugin *(*Filter_Info_Function)(unsigned long Index);

Sorry for the missing explanation. This function is similar to the
LADSPA_Descriptor_Function() and is the entry point into the plugin
file. The concept is the same as for LADSPA, that is in one file there
can be multiple filters and the get_Filter_Info() functions retrieves
the filter_plugin structures for the various filters based on an index.
If an invalid index is specified, the function returns NULL.



In an earlier mail you wrote:

It is not fixed yet what parameter_get_description() will exactly return, so the list
above has to be viewed as an example.
To get closer a final decision, I have this comment: I already
complained that min/max may not always make sense; I suggest solving
this by wrapping the min/max information in a structure whose contents
are type specific. That allows us to extend the filter metadata
structure with non-type-specific stuff later if necessary, and new
type-specific stuff can be added too without breaking old code.

Yes, something like {{name}{type}{default value}{Array of type specific information}}
sounds like a good approach.

For reference I attached my first plugin, so that you can see what
a filter implementation would look like.

#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <stdbool.h>
#include <assert.h>
#include <errno.h>
#include <math.h>
#include <ctype.h>
#include <locale.h>
#include <stdarg.h>
#include <stdio.h>

#include "filter-plugin.h"

#define my_streq(a,b) (!strcmp((a),(b)))

/* Parameter set for amplifier. */
typedef struct {
    float gain;
    bool bypass;
} Parameters;

/* The amplifier structure switches param between the two
 * parameter sets param1 and param2 */
typedef struct {
    Parameters *param;
    Parameters *param1;
    Parameters *param2;
    unsigned channels;
    void *module;
    Kill_Filter_Function kill_filter;
    char *sink_name;
    char *port_name;
} Amplifier;

struct filter_plugin *Filter1 = NULL;

/* Function needed to retrieve the filters in the plugin file. */
const struct filter_plugin *get_Filter_Info(unsigned long index) {

    if (index == 0)
        return Filter1;

    return NULL;
}

/* Utility functions, mainly stolen from pulseaudio */

/* Convert the string s to an unsigned integer in *ret_u. */
int my_atou(const char *s, uint32_t *ret_u) {
    char *x = NULL;
    unsigned long l;

    assert(s);
    assert(ret_u);

    /* strtoul() ignores leading spaces. We don't. */
    if (isspace((unsigned char)*s)) {
        errno = EINVAL;
        return -1;
    }

    /* strtoul() accepts strings that start with a minus sign. In that case the
     * original negative number gets negated, and strtoul() returns the negated
     * result. We don't want that kind of behaviour. strtoul() also allows a
     * leading plus sign, which is also a thing that we don't want. */
    if (*s == '-' || *s == '+') {
        errno = EINVAL;
        return -1;
    }

    errno = 0;
    l = strtoul(s, &x, 0);

    /* If x doesn't point to the end of s, there was some trailing garbage in
     * the string. If x points to s, no conversion was done (empty string). */
    if (!x || *x || x == s || errno) {
        if (!errno)
            errno = EINVAL;
        return -1;
    }

    if ((uint32_t) l != l) {
        errno = ERANGE;
        return -1;
    }

    *ret_u = (uint32_t) l;

    return 0;
}

/* Convert the string s to a double in *ret_d. */
int my_atod(const char *s, double *ret_d) {
    char *x = NULL;
    double f;
    char *tmp, *y;
    struct lconv *locale;

    assert(s);
    assert(ret_d);

    /* strtod() ignores leading spaces. We don't. */
    if (isspace((unsigned char)*s)) {
        errno = EINVAL;
        return -1;
    }

    /* strtod() accepts leading plus signs, but that's ugly, so we don't allow
     * that. */
    if (*s == '+') {
        errno = EINVAL;
        return -1;
    }

    tmp = strdup(s);

    /* Get decimal separator for current locale */
    locale = localeconv();

    /* Replace decimal point with the correct character for the
     * current locale. This assumes that no thousand separator
     * is used. */
    for (y = tmp; *y; y++) {
        if (*y == '.' || *y == ',')
            *y = *locale->decimal_point;
     }

    errno = 0;
    f = strtod(tmp, &x);

    /* If x doesn't point to the end of s, there was some trailing garbage in
     * the string. If x points to s, no conversion was done (at least an empty
     * string can trigger this). */
    if (!x || *x || x == tmp || errno) {
        free(tmp);
        if (!errno)
            errno = EINVAL;
        return -1;
    }
    free(tmp);

    if (isnan(f)) {
        errno = EINVAL;
        return -1;
    }

    *ret_d = f;

    return 0;
}

/* The following function is based on an example from the GNU libc
 * documentation. This function is similar to GNU's asprintf(). */
char *my_sprintf_malloc(const char *format, ...) {
    size_t size = 100;
    char *c = NULL;

    assert(format);

    for(;;) {
        int r;
        va_list ap;

        c = realloc(c, size);

        va_start(ap, format);
        r = vsnprintf(c, size, format, ap);
        va_end(ap);

        c[size-1] = 0;

        if (r > -1 && (size_t) r < size)
            return c;

        if (r > -1)    /* glibc 2.1 */
            size = (size_t) r+1;
        else           /* glibc 2.0 */
            size *= 2;
    }
}

static char *double_to_string(double value, int precision) {
    char *s, *buf;

    /* Convert to string */
    buf = my_sprintf_malloc("%.*g",  precision, value);

    /* Replace "," with "." */
    for (s = buf; *s; s++) {
        if (*s == ',') {
            *s = '.';
            break;
        }
    }

    return buf;
}

static inline unsigned bool_to_unsigned(bool value) {

    return value ? 1:0;
}

/* Filter functions */

static void *create_filter(unsigned input_channels, int *input_map,
                           unsigned output_channels, int *output_map,
                           Kill_Filter_Function kill_filter, void *module, unsigned sample_rate) {
    Amplifier *amp;

    /* Number of input channels must match number of output channels. */
    if (input_channels != output_channels || !input_channels)
        return NULL;

    /* Allocate memory */
    amp = (Amplifier *)malloc(sizeof(Amplifier));
    amp->param1 = (Parameters *)malloc(sizeof(Parameters));
    amp->param2 = (Parameters *)malloc(sizeof(Parameters));

    /* Set default parameters */
    amp->param1->gain = 1;
    amp->param1->bypass = true;
    amp->param2->gain = 1;
    amp->param2->bypass = true;
    amp->param = amp->param1;

    amp->channels = input_channels;
    amp->module = module;
    amp->kill_filter = kill_filter;

    amp->sink_name = NULL;
    amp->port_name = NULL;

    return amp;
}

static void delete_filter(void *filter_handle) {
    Amplifier *amp;

    assert(amp = filter_handle);

    if (amp->sink_name)
        free(amp->sink_name);
    if (amp->port_name)
        free(amp->port_name);

    free(amp->param1);
    free(amp->param2);
    free(amp);

}

/* Called from I/O thread context */
static void filter_process_chunk(float *src, float *dst, unsigned count, void *filter_handle) {
    Amplifier *amp;
    unsigned c;
    float gain;

    assert(amp = filter_handle);
    assert(count);

    count *= amp->channels;

    /* If bypass is set, copy source to destination. */
    if (amp->param->bypass) {
        memcpy(dst, src, count * sizeof(float));
        return;
    }

    /* As an example, multiply by gain. */
    gain = amp->param->gain;

    for (c = 0; c < count; c++)
        *(dst + c) = *(src + c) * gain;
}

static void *update_filter_parameters(void *parameters, void *filter_handle) {
    Amplifier *amp;

    assert(amp = filter_handle);

    amp->param = (Parameters *)parameters;
    return NULL;
}

static void *parameter_set(const char *identifier, const char *value, void *filter_handle) {
    Amplifier *amp;
    Parameters *param;

    assert(amp = filter_handle);

    /* Choose parameter set to change */
    param = amp->param1;
    if (amp->param == amp->param1)
        param = amp->param2;

    /* Copy old values */
    param->gain = amp->param->gain;
    param->bypass = amp->param->bypass;

    if (my_streq(identifier, "0") || my_streq(identifier, "Gain")) {
        double gain;

        /* If string is empty, set default value */
        param->gain = 1;
        if (*value) {
            if (my_atod(value, &gain) < 0)
                return NULL;
            param->gain = gain;
        }

    } else if (my_streq(identifier, "1") || my_streq(identifier, "Bypass")) {
        uint32_t bypass;

        /* If string is empty, set default value */
        param->bypass = true;
        if (*value) {
            if (my_atou(value, &bypass) < 0)
                return NULL;
            if (!bypass)
                param->bypass = false;
        }

    } else
        return NULL;

    return param;
}

static void *parameter_set_all(const char **all_params, int param_count, void *filter_handle) {
    Amplifier *amp;
    Parameters *param;
    double gain;
    uint32_t bypass;

    assert(amp = filter_handle);

    /* Choose parameter set to change */
    param = amp->param1;
    if (amp->param == amp->param1)
        param = amp->param2;

    /* Return default parameter set if all_params is NULL */
    if (!all_params) {
        param->gain = 1;
        param->bypass = true;
        return param;
    }

    if (param_count != 2)
        return NULL;

    /* If string is empty, set default value */
    param->gain = 1;
    if (*all_params[0]) {
        if (my_atod(all_params[0], &gain) < 0)
            return NULL;
        param->gain = gain;
    }

    /* If string is empty, set default value */
    param->bypass = true;
    if (*all_params[1]) {
        if (my_atou(all_params[1], &bypass) < 0)
            return NULL;
        if (!bypass)
            param->bypass = false;
    }

    return param;
}

static char *parameter_get(const char *identifier, void *filter_handle) {
    Amplifier *amp;

    assert(amp = filter_handle);

    if (my_streq(identifier, "0") || my_streq(identifier, "Gain")) {
        char *value, *ret;

        value = double_to_string(amp->param->gain, 8);
        ret = my_sprintf_malloc("{{%s}}", value);
        free(value);
        return ret;

    } else if (my_streq(identifier, "1") || my_streq(identifier, "Bypass"))

        return my_sprintf_malloc("{{%u}}", bool_to_unsigned(amp->param->bypass));

    return NULL;
}

static char *parameter_get_all(void *filter_handle) {
    Amplifier *amp;
    char *value, *ret;

    assert(amp = filter_handle);

    value = double_to_string(amp->param->gain, 8);

    ret = my_sprintf_malloc("{{%s}{%u}}", value, bool_to_unsigned(amp->param->bypass));

    free(value);
    return ret;
}

static char *filter_description(void *filter_handle) {
    return strdup("{{Plugin sink - simple amplifier filter example}{{{Gain}{float}{1}{0.1}{5}}{{Bypass}{bool}{1}}}}");
}

static void output_changed(const char *sink_name, const char *port_name, void *filter_handle) {
    Amplifier *amp;

    assert(amp = filter_handle);

    if (amp->sink_name)
        free(amp->sink_name);
    if (amp->port_name)
        free(amp->port_name);

    amp->sink_name = strdup(sink_name);
    amp->port_name = strdup(port_name);
}

/* Message handler for some example message commands */
static int filter_message_handler(const char *message, char *message_parameters, char **response, void *filter_handle) {
    Amplifier *amp;

    assert(amp = filter_handle);

    if (my_streq(message, "kill-filter")) {

        *response = strdup("{{Filter killed}}");
        amp->kill_filter(amp->module);
        return OK;
    }

    if (my_streq(message, "get-output-info")) {
        if (amp->sink_name)
            *response = my_sprintf_malloc("{{%s}{%s}}", amp->sink_name, amp->port_name);
        else
            *response = strdup("{{Unset}}");
        return OK;
    }

    if (my_streq(message, "get-messages")) {
        *response = strdup("{{parameter-get}{parameter-set}{parameter-get-all}{parameter-set-all}{parameter-get-description}{kill-filter}{get-messages}{get-output-info}}");
        return OK;
    }

    return -ERR_NOTIMPLEMENTED;
}

/* Library init and exit functions */

/* Function that is called when the library is loaded.
 * Creates filter plugin structures. */
void __attribute__ ((constructor)) initLibrary(void) {

    Filter1 = (filter_plugin *)malloc(sizeof(filter_plugin));

    Filter1->name = strdup("PA-Amplifier");
    Filter1->input_channels = 0;
    Filter1->output_channels = 0;
    Filter1->desc_head = strdup("Amplifier Sink");
    Filter1->filter_type = strdup("amplifier");
    Filter1->max_chunk_size = 0;
    Filter1->fixed_block_size = 0;
    /* Rewinding can be enabled here because the filter is stateless.
     * For filters that are not stateless and do not implement rewinding,
     * the value should be set to -1. */
    Filter1->max_rewind = 0;

    Filter1->rewind_filter = NULL;
    Filter1->process_chunk = filter_process_chunk;
    Filter1->get_extra_latency = NULL;
    Filter1->create_filter = create_filter;
    Filter1->delete_filter = delete_filter;
    Filter1->activate_filter = NULL;
    Filter1->deactivate_filter = NULL;
    Filter1->output_changed = output_changed;
    Filter1->update_filter_parameters = update_filter_parameters;
    Filter1->parameter_free = NULL;
    Filter1->parameter_get = parameter_get;
    Filter1->parameter_set = parameter_set;
    Filter1->parameter_get_all = parameter_get_all;
    Filter1->parameter_set_all = parameter_set_all;
    Filter1->parameter_get_description = filter_description;
    Filter1->filter_message_handler = filter_message_handler;
}

/* Function that is called when the library is closed.
 * Removes structures created by initLibrary(). */
void __attribute__ ((destructor)) cleanUpLibrary(void) {

    free((char *)Filter1->name);
    free((char *)Filter1->desc_head);
    free((char *)Filter1->filter_type);
    free(Filter1);
}
_______________________________________________
pulseaudio-discuss mailing list
pulseaudio-discuss@xxxxxxxxxxxxxxxxxxxxx
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss

[Index of Archives]     [Linux Audio Users]     [AMD Graphics]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux