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);
}



Thursday, May 24, 2012

Welcome

Welcome to my blog!

I've started this blog to document the progress on my Google Summer of Code 2012 project for PulseAudio, but I will hopefully keep posting about other (more or less) interesting things I'm working on.

The project aims to provide basic GUI integration and user feedback for device discovery events, through the Desktop Notification specification. This will come as a PulseAudio module that will communicate through D-Bus to the notification system provided by the desktop environment. The module will be subscribing to the server to receive device discovery events and will be relaying that information to the user. If the user decides to take an action with respect to that device, this will be communicated back to the module (through the D-Bus Desktop Notification API) and the module will change the server state accordingly. I have split the development into the following tasks:
  1. write a simple module to detect new devices;
  2. add basic interaction with D-Bus through the Desktop Notification specification;
  3. implement user feedback through DN;
  4. implement persistence of user settings through the PulseAudio database;
  5. improve the user intreface, from a usability point of view:
    • should the user set a whole device as default or sinks/sources;
    • should there be separate notifications for each of the sinks and sources of a device;
    • what happens if a user ignores the notification etc.
  6. adapt the zeroconf module to integrate with this module well (e.g. add authentication prompt);
  7. inspect other available user feedback/notification systems, such as Ubuntu's, and extend the module to provide for those mechanisms as well;
  8. make some adjustments to the existing GUIs (such as disabling volume controls when pass-through mode is enabled).
I will start coding in about a week and I will try to post updates on my progress weekly. The posts will not be aimed only for those who have a good understanding of PulseAudio; I will try to explain what I discover and learn so that others who would want to start coding for PulseAudio can read through the posts easily and understand how everything works.

I'm looking forward to this summer and to working on this project!