PCM External Plugin SDK

External Plugins

The external plugins are implemented in a shared object file located at /usr/lib/alsa-lib (the exact location depends on the build option and asoundrc configuration). It has to be the file like libasound_module_pcm_MYPLUGIN.so, where MYPLUGIN corresponds to your own plugin name.

The entry point of the plugin is defined via SND_PCM_PLUGIN_DEFINE_FUNC() macro. This macro defines the function with a proper name to be referred from alsa-lib. The function takes the following 6 arguments:

int (snd_pcm_t **pcmp, const char *name, snd_config_t *root,
    snd_config_t *conf, snd_pcm_stream_t stream, int mode)

The first argument, pcmp, is the pointer to store the resultant PCM handle. The arguments name, root, stream and mode are the parameters to be passed to the plugin constructor. The conf is the configuration tree for the plugin. The arguments above are defined in the macro itself, so don’t use variables with the same names to shadow parameters.

After parsing the configuration parameters in the given conf tree, usually you will call the external plugin API function, snd_pcm_extplug_create() or snd_pcm_ioplug_create(), depending on the plugin type. The PCM handle must be filled *pcmp in return. Then this function must return either a value 0 when succeeded, or a negative value as the error code.

Finally, add SND_PCM_PLUGIN_SYMBOL() with the name of your plugin as the argument at the end. This defines the proper versioned symbol as the reference.

The typical code would look like below:

struct myplug_info {
    snd_pcm_extplug_t ext;
    int my_own_data;
    ...
};

SND_PCM_PLUGIN_DEFINE_FUNC(myplug)
{
    snd_config_iterator_t i, next;
    snd_config_t *slave = NULL;
    struct myplug_info *myplug;
    int err;

    snd_config_for_each(i, next, conf) {
        snd_config_t *n = snd_config_iterator_entry(i);
        const char *id;
        if (snd_config_get_id(n, &id) < 0)
            continue;
        if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0)
            continue;
        if (strcmp(id, "slave") == 0) {
            slave = n;
            continue;
        }
        if (strcmp(id, "my_own_parameter") == 0) {
            ....
            continue;
        }
        SNDERR("Unknown field %s", id);
        return -EINVAL;
    }

    if (! slave) {
        SNDERR("No slave defined for myplug");
        return -EINVAL;
    }

    myplug = calloc(1, sizeof(*myplug));
    if (myplug == NULL)
        return -ENOMEM;

    myplug->ext.version = SND_PCM_EXTPLUG_VERSION;
    myplug->ext.name = "My Own Plugin";
    myplug->ext.callback = &my_own_callback;
    myplug->ext.private_data = myplug;
    ....

    err = snd_pcm_extplug_create(&myplug->ext, name, root, conf, stream, mode);
    if (err < 0) {
        myplug_free(myplug);
        return err;
    }

     *pcmp = myplug->ext.pcm;
    return 0;
}

SND_PCM_PLUGIN_SYMBOL(myplug);

Read the codes in alsa-plugins package for the real examples.

External Plugin: Filter-Type Plugin

The filter-type plugin is a plugin to convert the PCM signals from the input and feeds to the output. Thus, this plugin always needs a slave PCM as its output.

The plugin can modify the format and the channels of the input/output PCM. It can not modify the sample rate (because of simplicity reason).

The following fields have to be filled in extplug record before calling snd_pcm_extplug_create() : version, name, callback. Otherfields are optional and should be initialized with zero.

The constant SND_PCM_EXTPLUG_VERSION must be passed to the version field for the version check in alsa-lib. A non-NULL ASCII string has to be passed to the name field. The callback field contains the table of callback functions for this plugin (defined as snd_pcm_extplug_callback_t).

The driver can set an arbitrary value (pointer) to private_data field to refer its own data in the callbacks.

The rest fields are filled by snd_pcm_extplug_create(). The pcm field is the resultant PCM handle. The others are the current status of the PCM.

The callback functions in snd_pcm_extplug_callback_t define the real behavior of the driver. At least, transfer callback must be given. This callback is called at each time certain size of data block is transfered to the slave PCM. Other callbacks are optional.

The close callback is called when the PCM is closed. If the plugin allocates private resources, this is the place to release them again. The hw_params and hw_free callbacks are called at snd_pcm_hw_params() and snd_pcm_hw_free() API calls, respectively. The last, dump callback, is called for printing the information of the given plugin.

The init callback is called when the PCM is at prepare state or any initialization is issued. Use this callback to reset the PCM instance to a sane initial state.

The hw_params constraints can be defined via either snd_pcm_extplug_set_param_minmax() and snd_pcm_extplug_set_param_list() functions after calling snd_pcm_extplug_create(). The former defines the minimal and maximal acceptable values for the given hw_params parameter (SND_PCM_EXTPLUG_HW_XXX). This function can’t be used for the format parameter. The latter function specifies the available parameter values as the list. As mentioned above, the rate can’t be changed. Only changeable parameters are sample format and channels.

To define the constraints of the slave PCM configuration, use either snd_pcm_extplug_set_slave_param_minmax() and snd_pcm_extplug_set_slave_param_list(). The arguments are as same as former functions.

To clear the parameter constraints, call snd_pcm_extplug_params_reset() function.

External Plugin: I/O Plugin

The I/O-type plugin is a PCM plugin to work as the input or output terminal point, i.e. as a user-space PCM driver.

The new plugin is created via snd_pcm_ioplug_create() function. The first argument is a pointer of the pluging information. Some of this struct must be initialized in prior to call snd_pcm_ioplug_create(). Then the function fills other fields in return. The rest arguments, name, stream and mode, are usually identical with the values passed from the ALSA plugin constructor.

The following fields are mandatory: version, name, callback. Otherfields are optional and should be initialized with zero.

The constant SND_PCM_IOPLUG_VERSION must be passed to the version field for the version check in alsa-lib. A non-NULL ASCII string has to be passed to the name field. The callback field contains the table of callback functions for this plugin (defined as snd_pcm_ioplug_callback_t).

flags field specifies the optional bit-flags. poll_fd and poll_events specify the poll file descriptor and the corresponding poll events (POLLIN, POLLOUT) for the plugin. If the plugin requires multiple poll descriptors or poll descriptor(s) dynamically varying, set poll_descriptors and poll_descriptors_count callbacks to the callback table. Then the poll_fd and poll_events field are ignored.

mmap_rw specifies whether the plugin behaves in the pseudo mmap mode. When this value is set to 1, the plugin creates always a local buffer and performs read/write calls using this buffer as if it’s mmapped. The address of local buffer can be obtained via snd_pcm_ioplug_mmap_areas() function. When poll_fd, poll_events and mmap_rw fields are changed after snd_pcm_ioplug_create(), call snd_pcm_ioplug_reinit_status() to reflect the changes.

The driver can set an arbitrary value (pointer) to private_data field to refer its own data in the callbacks.

The rest fields are filled by snd_pcm_ioplug_create(). The pcm field is the resultant PCM handle. The others are the current status of the PCM.

The callback functions in snd_pcm_ioplug_callback_t define the real behavior of the driver. At least, start, stop and pointer callbacks must be given. Other callbacks are optional. The start and stop callbacks are called when the PCM stream is started and stopped, repsectively. The pointer callback returns the current DMA position, which may be called at any time.

The transfer callback is called when any data transfer happens. It receives the area array, offset and the size to transfer. The area array contains the array of snd_pcm_channel_area_t with the elements of number of channels.

When the PCM is closed, close callback is called. If the driver allocates any internal buffers, they should be released in this callback. The hw_params and hw_free callbacks are called when hw_params are set and reset, respectively. Note that they may be called multiple times according to the application. Similarly, sw_params callback is called when sw_params is set or changed.

The prepare, drain, pause and resume callbacks are called when snd_pcm_prepare(), snd_pcm_drain(), snd_pcm_pause(), and snd_pcm_resume() are called. The poll_descriptors_count and poll_descriptors callbacks are used to return the multiple or dynamic poll descriptors as mentioned above. The poll_revents callback is used to modify poll events. If the driver needs to mangle the native poll events to proper poll events for PCM, you can do it in this callback.

Finally, the dump callback is used to print the status of the plugin.

Note that some callbacks (start, stop, pointer, transfer and pause) may be called inside the internal pthread mutex, and they shouldn’t call the PCM functions again unnecessarily from the callback itself; otherwise it may lead to a deadlock.

The hw_params constraints can be defined via either snd_pcm_ioplug_set_param_minmax() and snd_pcm_ioplug_set_param_list() functions after calling snd_pcm_ioplug_create(). The former defines the minimal and maximal acceptable values for the given hw_params parameter (SND_PCM_IOPLUG_HW_XXX). This function can’t be used for the format parameter. The latter function specifies the available parameter values as the list.

To clear the parameter constraints, call snd_pcm_ioplug_params_reset() function.