Wednesday, June 13, 2012

Writing a simple PulseAudio module.

This post will describe the (little) progress I've made in the first week of coding and will try to provide a short but clear introduction to writing PulseAudio modules. A similar article exists on the PulseAudio wiki page, but I shall post this nevertheless as the next articles will be based on the code presented here. Finally, before checking the wiki page or going through this article, it is useful to know how to compile and run PulseAudio from git. Note that, as the README file says, the configure script must be run with the CFLAGS="-ggdb3 -O0" LDFLAGS="-ggdb3" environment variables, to be able to start from the build directory.

PulseAudio modules are required to have two functions: pa__init(pa_module*m) and pa__done(pa_module*m), which are called when the module is loaded and, respectively, unloaded (either by PulseAudio or because it has finished whatever it was doing). Other than  these functions, PulseAudio recognises a few others that provide some information about the module (such as the author or the description). However, these functions are not generally implemented directly, but rather created through the following macros defined in pulsecore/module.h:

PA_MODULE_AUTHOR(s)
PA_MODULE_DESCRIPTION(s)
PA_MODULE_USAGE(s)
PA_MODULE_VERSION(s)
PA_MODULE_DEPRECATED(s)
PA_MODULE_LOAD_ONCE(b)

pulsecore/module.h also contains the pa_module struct definition, which is explained on the Module API wiki page. One particularly relevant field of the structure is the userdata field, because it's the only way to carry information from pa__init to pa__done. This is why modules will generally define a userdata struct that contains all the relevant module data, and that is used by pa__done to free whatever memory is allocated in pa__init. This struct is also used to pass data to core hooks that handle various events.

Another important field in the pa_module structure is the core field. This contains a pa_core struct, which is defined in pulsecore/core.h and is described on the Core API wiki page. This field is particularly relevant because it is used when creating hooks.

Creating and freeing hook slots is done through two functions defined in pulsecore/hook-list.h:

pa_hook_slot* pa_hook_connect(pa_hook *hook, pa_hook_priority_t prio, pa_hook_cb_t cb, void *data);
void pa_hook_slot_free(pa_hook_slot *slot);

The core hook types are stored in the hooks array in pa_core, which is indexed according to the pa_core_hook enum defined in pulsecore/core.h. As an example, the hook that is called after a card has been created is referred to by m->core->hooks[PA_CORE_HOOK_CARD_PUT].

pa_hook_priority_t is another enum defined in pulsecore/hook-list.h and can currently take the values PA_HOOK_EARLY, PA_HOOK_NORMAL or PA_HOOK_LATE. However, these are just integers and the call can be adjusted to control the order in which hooks are run (e.g. PA_HOOK_LATE+10 will run after PA_HOOK_LATE).

The pa_hook_cb_t is a function pointer defined in pulsecore/hook-list.h, with the following definition:

typedef pa_hook_result_t (*pa_hook_cb_t)(
        void *hook_data,
        void *call_data,
        void *slot_data);
and the last parameter is used for whatever user data needs to be passed from the module to the hook callback function.

Putting all this together, the following module is able to detect and log whenever a new card is plugged in:

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <pulse/proplist.h>
#include <pulse/xmalloc.h>

#include <pulsecore/card.h>
#include <pulsecore/core.h>
#include <pulsecore/log.h>
#include <pulsecore/module.h>

#include "module-desktop-notifications-symdef.h"

PA_MODULE_AUTHOR("Ștefan Săftescu");
PA_MODULE_DESCRIPTION("Integration with the Desktop Notifications specification.");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(TRUE);

struct userdata {
    pa_hook_slot *card_put_slot;
};


static pa_hook_result_t card_put_cb(pa_core *c, pa_card *card, void* userdata) {
    pa_log_info("Card detected: %s.", pa_proplist_gets(card->proplist, PA_PROP_DEVICE_DESCRIPTION));
    return PA_HOOK_OK;
}


int pa__init(pa_module*m) {
    struct userdata *u;
   
    m->userdata = u = pa_xnew(struct userdata, 1);
   
    u->card_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CARD_PUT], PA_HOOK_LATE, (pa_hook_cb_t) card_put_cb, u);
   
    return 0;
}

void pa__done(pa_module*m) {
    struct userdata *u;
    pa_assert(m);

    if (!(u = m->userdata))
        return;

    if (u->card_put_slot)
        pa_hook_slot_free(u->card_put_slot);

    pa_xfree(u);
}