©  Jan Newmarch 2017

Jan Newmarch, Linux Sound Programming, 10.1007/978-1-4842-2496-0_6

6. PulseAudio

Jan Newmarch

(1)Oakleigh, Victoria, Australia

PulseAudio is a sound server, sitting above device drivers such as ALSA or OSS. It offers more capabilities than device drivers. PulseAudio is designed for consumer audio and makes it easy to use sound on desktops, laptops, and mobile devices. Multiple sources of sound can all play to the PulseAudio server, which will mix them together and play them. Low latency is not a design goal, so it is unsuitable for professional audio.

Resources

Here are some resources:

Starting, Stopping, and Pausing PulseAudio

If you have a current Linux system, PulseAudio is probably running. Test this by running the following from the command line :

ps agx | grep pulse

If you see a line like /usr/bin/pulseaudio --start --log-target=syslog, then it is running already.

If it isn’t running and you have it installed, then start it by using this:

pulseaudio --start

Stopping PulseAudio isn't so easy. Carla Schroder shows how at www.linuxplanet.com/linuxplanet/tutorials/7130/2 . The basic problem is that PulseAudio is set to respawn itself after it is killed. You have to turn that off by editing /etc/pulse/client.conf, changing autospawn = yes to autospawn = no, and setting daemon-binary to /bin/true. Then you can kill the process, remove it from the startup files, and so on.

If you want to run another sound system (such as Jack) for a short while, you may just want to pause PulseAudio. You do this by using pasuspender. This takes a command (after --) and will pause access by the PulseAudio server to the audio devices until the subcommand has finished. For example, the following will run the Jack server, with PulseAudio getting out of the way until it has finished:

pasuspender -- jackd

User Space Tools

PulseAudio has a range of user-level tools in addition to a programming API. These tools give information about the PulseAudio system and allow a variety of controls. The Gnome project also has a control center that is PulseAudio-aware. This section considers these tools.

paman

This shows you information about the PulseAudio server, its devices, and its clients. Figures 6-1 to 6-3 show the type of information it gives.

A435426_1_En_6_Fig1_HTML.jpg
Figure 6-1. Pulse Audio Manager server information
A435426_1_En_6_Fig2_HTML.jpg
Figure 6-2. Pulse Audio Manager device information
A435426_1_En_6_Fig3_HTML.jpg
Figure 6-3. Pulse Audio Manager client information

pavumeter

pavumeteris a simple meter for showing the input or output levels of the default devices. When run by pavumeter, it shows the playback device, as shown in Figure 6-4.

A435426_1_En_6_Fig4_HTML.jpg
Figure 6-4. Pulse Audio playback volume meter

If it is run by pavumeter --record, it shows the record device, as shown in Figure 6-5.

A435426_1_En_6_Fig5_HTML.jpg
Figure 6-5. Pulse Audio record volume meter

pavucontrol

pavucontrolallows you to control the input and output volumes of the different connected audio devices, as shown in Figure 6-6.

A435426_1_En_6_Fig6_HTML.jpg
Figure 6-6. Pulse Audio pavucontrol output devices

With these tabs, pavucontrol is a device-level mixer, able to control the overall volume to individual devices.

One of the special advantages of PulseAudio is that it can perform application-level mixing. If two audio sources write to the same PulseAudio device, the audio will be mixed to the output device. pavucontrol can show the multiple applications using the Playback tab, showing all applications or all streams currently being mixed. Each stream can have its channel volumes individually controlled.

For example, karaoke on the cheap can be done by setting the straight-through module for the microphone to speaker with the following:

pactl load-module module-loopback latency_msec=1

The karaoke file is played by a karaoke player such as kmid through timidity. Here’s an example:

kmid nightsin.kar

While these two are running, relative volumes can be controlled with the use of pavucontrol, as in Figure 6-7.

A435426_1_En_6_Fig7_HTML.jpg
Figure 6-7. Pulse Audio pavucontrol multiple devices

Gnome Control Center (Sound)

The command gnome-control-center sound allows full view and control of the attached sound devices, including selection of the default input and output devices . It looks like Figure 6-8.

A435426_1_En_6_Fig8_HTML.jpg
Figure 6-8. Gnome control center

parec/paplay/pacat

parec, paplay, and pacatare are command-line tools to record and play back sound files. They are all symbolic links to the same code, just differently named links. The default format is PCM s16. There are many options, but they don’t always do quite what you want them to do. For example, to play from the default record device to the default playback device with minimum latency, use the following:

pacat -r --latency-msec=1 | pacat -p --latency-msec=1

This actually has a latency of about 50ms.

pactl/pacmd

These two commands do basically the same thing. pacmd is the interactive version with more options. For example, pacmd with the command list-sinks includes the following:

        name: <alsa_output.pci-0000_00_1b.0.analog-stereo>
        driver: <module-alsa-card.c>
        flags: HARDWARE HW_MUTE_CTRL HW_VOLUME_CTRL DECIBEL_VOLUME LATENCY FLAT_VOLUME DYNAMIC_LATENCY
        state: SUSPENDED
        suspend cause: IDLE
        priority: 9959
        volume: 0:  93% 1:  93%
                0: -1.88 dB 1: -1.88 dB
                balance 0.00
        base volume: 100%
                     0.00 dB
        volume steps: 65537
        muted: no
        current latency: 0.00 ms
        max request: 0 KiB
        max rewind: 0 KiB
        monitor source: 1
        sample spec: s16le 2ch 44100Hz
        channel map: front-left,front-right
                     Stereo
        used by: 0
        linked by: 0
        configured latency: 0.00 ms; range is 16.00 .. 2000.00 ms
        card: 1 <alsa_card.pci-0000_00_1b.0>
        module: 5
        properties:
                alsa.resolution_bits = "16"
                device.api = "alsa"
                device.class = "sound"
                alsa.class = "generic"
                alsa.subclass = "generic-mix"
                alsa.name = "STAC92xx Analog"
                alsa.id = "STAC92xx Analog"
                alsa.subdevice = "0"
                alsa.subdevice_name = "subdevice #0"
                alsa.device = "0"
                alsa.card = "0"
                alsa.card_name = "HDA Intel PCH"
                alsa.long_card_name = "HDA Intel PCH at 0xe6e60000 irq 47"
                alsa.driver_name = "snd_hda_intel"
                device.bus_path = "pci-0000:00:1b.0"
                sysfs.path = "/devices/pci0000:00/0000:00:1b.0/sound/card0"
                device.bus = "pci"
                device.vendor.id = "8086"
                device.vendor.name = "Intel Corporation"
                device.product.id = "1c20"
                device.product.name = "6 Series/C200 Series Chipset Family High Definition Audio Controller"
                device.form_factor = "internal"
                device.string = "front:0"
                device.buffering.buffer_size = "352800"
                device.buffering.fragment_size = "176400"
                device.access_mode = "mmap+timer"
                device.profile.name = "analog-stereo"
                device.profile.description = "Analog Stereo"
                device.description = "Internal Audio Analog Stereo"
                alsa.mixer_name = "IDT 92HD90BXX"
                alsa.components = "HDA:111d76e7,10280494,00100102"
                module-udev-detect.discovered = "1"
                device.icon_name = "audio-card-pci"
        ports:
                analog-output: Analog Output (priority 9900)
                analog-output-headphones: Analog Headphones (priority 9000)
        active port: <analog-output>

Device Names

PulseAudio uses its own naming conventions. The names of source devices (such as microphones) can be found using code from the PulseAudio FAQ ( www.freedesktop.org/wiki/Software/PulseAudio/FAQ#How_do_I_record_stuff.3F ).

pactl list | grep -A2 'Source #' | grep 'Name: .*.monitor$' | cut -d" " -f2

On my system, this produces the following:

alsa_output.pci-0000_01_00.1.hdmi-stereo.monitor
alsa_output.pci-0000_00_1b.0.analog-stereo.monitor
alsa_input.pci-0000_00_1b.0.analog-stereo

Similarly, the output devices are found with the following:

pactl list | grep -A2 'Sink #' | grep 'Name: .*.monitor$' | cut -d" " -f2

This gives the following:

alsa_output.pci-0000_01_00.1.hdmi-stereo
alsa_output.pci-0000_00_1b.0.analog-stereo

Loopback Module

Using pactl, you can load the module module-loopback with this:

pactl load-module module-loopback latency_msec=1

When the module is loaded, sound is internally routed from the input device to the output device. The latency is effectively zero.

If you load this module into, say, your laptop, be careful about unplugging speakers, microphones, and so on. The internal speaker and microphone are close enough to set up a feedback loop. Unload module number N with this:

pactl unload-module N

(If you have forgotten the module number, just running pactl will list all modules so you can identify the loopback module.)

PulseAudio and ALSA

Output from pacmd shows that PulseAudio uses ALSA. The relationship is deeper: the default ALSA device is hw:0, but PulseAudio overrides that. In /etc/asound.conf is a hook to load /etc/alsa/pulse-default.conf, which contains the following:

pcm.!default {
    type pulse
    hint {
        description "Default"
    }
}

This replaces the default device with a PulseAudio module.

Opening the default ALSA device will actually call into PulseAudio, which will then call back into ALSA with the devices it chooses.

Programming with PulseAudio

The source for PulseAudio and its documentation is PulseAudio 2.0 ( http://freedesktop.org/software/pulseaudio/doxygen/index.html ). There are two ways of programming with PulseAudio: the simple API and the asynchronous API. Both are described in the following sections.

Simple API

PulseAudio has a “simple” API and a far more complex asynchronous API. The simple API may be good enough for your needs.

The simple API has a small set of functions , shown here:

pa_simple * pa_simple_new (const char *server, const char *name, pa_stream_direction_t dir, const char *dev, const char *stream_name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_buffer_attr *attr, int *error)
        Create a new connection to the server.
void    pa_simple_free (pa_simple *s)
        Close and free the connection to the server.
int     pa_simple_write (pa_simple *s, const void *data, size_t bytes, int *error)
        Write some data to the server.
int     pa_simple_drain (pa_simple *s, int *error)
        Wait until all data already written is played by the daemon.
int     pa_simple_read (pa_simple *s, void *data, size_t bytes, int *error)
        Read some data from the server.
pa_usec_t pa_simple_get_latency (pa_simple *s, int *error)
        Return the playback latency.
int     pa_simple_flush (pa_simple *s, int *error)
        Flush the playback buffer .

Play a File

The program shown next to play from a file to the default output device is from the PulseAudio site. The basic structure is as follows:

  1. Create a new playback stream (pa_simple_new).

  2. Read blocks from the file (read)…

  3. …Write them to the stream (pa_simple_write).

  4. Finish by flushing the stream (pa_simple_drain).

The program is pacat-simple.c. Rather weirdly, it does a dup2 to map the open file descriptor onto stdin and then reads from stdin. This isn’t necessary. Why not just read from the original file descriptor?

/***
 *   This file is part of PulseAudio.
 *
 *   PulseAudio is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published
 *   by the Free Software Foundation; either version 2.1 of the License,
 *   or (at your option) any later version.
 *
 *   PulseAudio 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 Lesser General Public License
 *   along with PulseAudio; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 *   USA.
 ****/


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


#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>


#include <pulse/simple.h>
#include <pulse/error.h>


#define BUFSIZE 1024

int main(int argc, char*argv[]) {

    // set to NULL for default output device
    char *device = "alsa_output.pci-0000_00_1b.0.analog-stereo";


    /* The Sample format to use */
    static const pa_sample_spec ss = {
        .format = PA_SAMPLE_S16LE,
        .rate = 44100,
        .channels = 2
    };


    pa_simple *s = NULL;
    int ret = 1;
    int error;


    /* replace STDIN with the specified file if needed */
    if (argc > 1) {
        int fd;


        if ((fd = open(argv[1], O_RDONLY)) < 0) {
            fprintf(stderr, __FILE__": open() failed: %s ", strerror(errno));
            goto finish;
        }


        if (dup2(fd, STDIN_FILENO) < 0) {
            fprintf(stderr, __FILE__": dup2() failed: %s ", strerror(errno));
            goto finish;
        }


        close(fd);
    }


    /* Create a new playback stream */
    if (!(s = pa_simple_new(NULL, argv[0], PA_STREAM_PLAYBACK, device, "playback", &ss, NULL, NULL, &error))) {
        fprintf(stderr, __FILE__": pa_simple_new() failed: %s ", pa_strerror(error));
        goto finish;
    }


    for (;;) {
        uint8_t buf[BUFSIZE];
        ssize_t r;


#if 1
        pa_usec_t latency;


        if ((latency = pa_simple_get_latency(s, &error)) == (pa_usec_t) -1) {
            fprintf(stderr, __FILE__": pa_simple_get_latency() failed: %s ", pa_strerror(error));
            goto finish;
        }


        fprintf(stderr, "%0.0f usec     ", (float)latency);
#endif


        /* Read some data ... */
        if ((r = read(STDIN_FILENO, buf, sizeof(buf))) <= 0) {
            if (r == 0) /* EOF */
                break;


            fprintf(stderr, __FILE__": read() failed: %s ", strerror(errno));
            goto finish;
        }


        /* ... and play it */
        if (pa_simple_write(s, buf, (size_t) r, &error) < 0) {
            fprintf(stderr, __FILE__": pa_simple_write() failed: %s ", pa_strerror(error));
            goto finish;
        }
    }


    /* Make sure that every single sample was played */
    if (pa_simple_drain(s, &error) < 0) {
        fprintf(stderr, __FILE__": pa_simple_drain() failed: %s ", pa_strerror(error));
        goto finish;
    }


    ret = 0;

finish:

    if (s)
        pa_simple_free(s);


    return ret;
}

Record to a File

The program shown next to record to a file from the default input device is from the PulseAudio site. It’s called parec-simple.c. The basic structure is as follows:

  1. Create a new recording stream (pa_simple_new).

  2. Read blocks from the stream (pa_simple_read)…

  3. …Write them to the output (write).

  4. Finish by releasing the stream (pa_simple_free).

Note that you need to tell PulseAudio the format to write the data, using a pa_sample_spec. Here I chose two-channel, 44100Hz, and PCM 16-bit little-endian format.

/***
  This file is part of PulseAudio.


  PulseAudio is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as published
  by the Free Software Foundation; either version 2.1 of the License,
  or (at your option) any later version.


  PulseAudio 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 Lesser General Public License
  along with PulseAudio; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  USA.
***/


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


#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>


#include <pulse/simple.h>
#include <pulse/error.h>


#define BUFSIZE 1024

/* A simple routine calling UNIX write() in a loop */
static ssize_t loop_write(int fd, const void*data, size_t size) {
    ssize_t ret = 0;


    while (size > 0) {
        ssize_t r;


        if ((r = write(fd, data, size)) < 0)
            return r;


        if (r == 0)
            break;


        ret += r;
        data = (const uint8_t*) data + r;
        size -= (size_t) r;
    }


    return ret;
}


int main(int argc, char*argv[]) {
    /* The sample type to use */
    static const pa_sample_spec ss = {
        .format = PA_SAMPLE_S16LE,
        .rate = 44100,
        .channels = 2
    };
    pa_simple *s = NULL;
    int ret = 1;
    int error;


    /* Create the recording stream */
    if (!(s = pa_simple_new(NULL, argv[0], PA_STREAM_RECORD, NULL, "record", &ss, NULL, NULL, &error))) {
        fprintf(stderr, __FILE__": pa_simple_new() failed: %s ", pa_strerror(error));
        goto finish;
    }


    for (;;) {
        uint8_t buf[BUFSIZE];


        /* Record some data ... */
        if (pa_simple_read(s, buf, sizeof(buf), &error) < 0) {
            fprintf(stderr, __FILE__": pa_simple_read() failed: %s ", pa_strerror(error));
            goto finish;
        }


        /* And write it to STDOUT */
        if (loop_write(STDOUT_FILENO, buf, sizeof(buf)) != sizeof(buf)) {
            fprintf(stderr, __FILE__": write() failed: %s ", strerror(errno));
            goto finish;
        }
    }


    ret = 0;

finish:

    if (s)
        pa_simple_free(s);


    return ret;
}

The output from this is a PCM s16 file. You can convert it to another format using sox (for example, sox -c 2 -r 44100 tmp.s16 tmp.wav), or you can import it as raw data into Audacity and play it directly.

How good are these for real-time audio? The first program can show the latency (change #if 0 to #if 1). This code can also be copied into the second one. The results are not good.

  • Recording has a latency of 11ms on my laptop.

  • Playback has a latency of 130ms.

Play from Source to Sink

You can combine the two programs to copy from the microphone to the speaker using a record and a playback stream. The program is pa-mic-2-speaker-simple.c, shown here:

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


#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>


#include <pulse/simple.h>
#include <pulse/error.h>


#define BUFSIZE 32

int main(int argc, char*argv[]) {

    /* The Sample format to use */
    static const pa_sample_spec ss = {
        .format = PA_SAMPLE_S16LE,
        .rate = 44100,
        .channels = 2
    };


    pa_simple *s_in, *s_out = NULL;
    int ret = 1;
    int error;


    /* Create a new playback stream */
    if (!(s_out = pa_simple_new(NULL, argv[0], PA_STREAM_PLAYBACK, NULL, "playback", &ss, NULL, NULL, &error))) {
        fprintf(stderr, __FILE__": pa_simple_new() failed: %s ", pa_strerror(error));
        goto finish;
    }


      if (!(s_in = pa_simple_new(NULL, argv[0], PA_STREAM_RECORD, NULL, "record", &ss, NULL, NULL, &error))) {
        fprintf(stderr, __FILE__": pa_simple_new() failed: %s ", pa_strerror(error));
        goto finish;
    }


    for (;;) {
        uint8_t buf[BUFSIZE];
        ssize_t r;


#if 1
        pa_usec_t latency;


        if ((latency = pa_simple_get_latency(s_in, &error)) == (pa_usec_t) -1) {
            fprintf(stderr, __FILE__": pa_simple_get_latency() failed: %s ", pa_strerror(error));
            goto finish;
        }


        fprintf(stderr, "In:  %0.0f usec     ", (float)latency);

        if ((latency = pa_simple_get_latency(s_out, &error)) == (pa_usec_t) -1) {
            fprintf(stderr, __FILE__": pa_simple_get_latency() failed: %s ", pa_strerror(error));
            goto finish;
        }


        fprintf(stderr, "Out: %0.0f usec     ", (float)latency);
#endif


        if (pa_simple_read(s_in, buf, sizeof(buf), &error) < 0) {

            fprintf(stderr, __FILE__": read() failed: %s ", strerror(errno));
            goto finish;
        }


        /* ... and play it */
        if (pa_simple_write(s_out, buf, sizeof(buf), &error) < 0) {
            fprintf(stderr, __FILE__": pa_simple_write() failed: %s ", pa_strerror(error));
            goto finish;
        }
    }


    /* Make sure that every single sample was played */
    if (pa_simple_drain(s_out, &error) < 0) {
        fprintf(stderr, __FILE__": pa_simple_drain() failed: %s ", pa_strerror(error));
        goto finish;
    }


    ret = 0;

finish:

    if (s_in)
        pa_simple_free(s_in);
    if (s_out)
        pa_simple_free(s_out);


    return ret;
}

Try running this and you will discover that the latency is noticeable and unsatisfactory.

Asynchronous API

The simple API is, well, simple. By contrast, the asynchronous API is large and complex. There are also few examples of using this API.

Nearly all interaction with this API is asynchronous. A call is made to the PulseAudio server , and when the response is ready, a library invokes a callback function that you will have passed to it when making the library call. This avoids the need for user code to either block or make polling calls.

The essential structure is as follows:

  1. Create a PulseAudio main loop (synchronous: pa_mainloop_new).

  2. Get the mainloop API object, which is a table of mainloop functions (synchronous: pa_mainloop_get_api).

  3. Get a context object to talk to the PulseAudio server (synchronous: pa_context_new).

  4. Establish a connection to the PulseAudio server. This is asynchronous: pa_context_connect.

  5. Register a callback for context state changes from the server: pa_context_set_state_callback.

  6. Commence the event-processing loop (pa_mainloop_run).

  7. Within the context state callback, determine what state has changed. For example, the connection has been established.

  8. Within this callback, set up, record, or playback streams.

  9. Establish further callbacks for these streams.

  10. Within the stream callbacks, do more processing, such as saving a recording stream to file.

Steps 1–7 will be common to most applications. The context state callback will be called in response to changes in the server. These are state changes such as PA_CONTEXT_CONNECTIN, PA_CONTEXT_SETTING_NAME, and so on. The change of relevance to most applications will be PA_CONTEXT_READY. This signifies that the application can make requests of the server in its steady state.

In step 8, the application will set its own behavior. This is done by setting up further callback functions for various operations, such as listing devices or playing audio.

List of Devices

The function pa_context_get_sink_info_list will set up a callback function to list source devices with the following:

pa_context_get_sink_info_list(c, sinklist_cb, NULL)

where c is the context, sinklist_cb is the application’s callback, and NULL is user data passed to the callback.

The callback is called as follows:

void sinklist_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata)

The parameter eol can take three values: negative means a failure of some kind, zero means a valid entry for pa_sink_info, and positive means that there are no more valid entries in the list .

The structure pa_sink_info is defined as follows:

struct {
  const char *  name;
  uint32_t      index;
  const char *  description;
  pa_sample_spec        sample_spec;
  pa_channel_map        channel_map;
  uint32_t      owner_module;
  pa_cvolume    volume;
  int   mute;
  uint32_t      monitor_source;
  const char *  monitor_source_name;
  pa_usec_t     latency;
  const char *  driver;
  pa_sink_flags_t       flags;
  pa_proplist *         proplist;
  pa_usec_t     configured_latency;
  pa_volume_t   base_volume;
  pa_sink_state_t       state;
  uint32_t      n_volume_steps;
  uint32_t      card;
  uint32_t      n_ports;
  pa_sink_port_info **  ports;
  pa_sink_port_info *   active_port;
  uint8_t       n_formats;
  pa_format_info **     formats;
} pa_sink_info

Further information about this structure is maintained in the Doxygen “pa_sink_info Struct Reference” ( http://freedesktop.org/software/pulseaudio/doxygen/structpa__sink__info.html ).

For information, the major fields are name and description. The index is an opaque index into some data structure and is used in many PulseAudio functions. The proplist is a map of general information that may contain interesting information. This can be retrieved by iterating through the map .

There are similar callback and data structures for input devices.

A program to list input and output devices current when the application connects to the server is palist_devices.c:
/**
 * palist_devices.c
 * Jan Newmarch
 */


#include <stdio.h>
#include <string.h>
#include <pulse/pulseaudio.h>


#define _(x) x

// quit when this reaches 2
int no_more_sources_or_sinks = 0;


int ret;

pa_context *context;

void show_error(char *s) {
    fprintf(stderr, "%s ", s);
}


void print_properties(pa_proplist *props) {
    void *state = NULL;


    printf("  Properties are: ");
    while (1) {
        char *key;
        if ((key = pa_proplist_iterate(props, &state)) == NULL) {
            return;
        }
        char *value = pa_proplist_gets(props, key);
        printf("   key: %s, value: %s ", key, value);
    }
}


/**
 * print information about a sink
 */
void sinklist_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {


    // If eol is set to a positive number, you're at the end of the list
    if (eol > 0) {
        printf("**No more sinks ");
        no_more_sources_or_sinks++;
        if (no_more_sources_or_sinks == 2)
            exit(0);
        return;
    }


    printf("Sink: name %s, description %s ", i->name, i->description);
    print_properties(i->proplist);
}


/**
 * print information about a source
 */
void sourcelist_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) {
    if (eol > 0) {
        printf("**No more sources ");
        no_more_sources_or_sinks++;
        if (no_more_sources_or_sinks == 2)
            exit(0);
        return;
    }


    printf("Source: name %s, description %s ", i->name, i->description);
    print_properties(i->proplist);
}


void context_state_cb(pa_context *c, void *userdata) {

    switch (pa_context_get_state(c)) {
    case PA_CONTEXT_UNCONNECTED:
    case PA_CONTEXT_CONNECTING:
    case PA_CONTEXT_AUTHORIZING:
    case PA_CONTEXT_SETTING_NAME:
        break;


    case PA_CONTEXT_READY: {
        pa_operation *o;


        // set up a callback to tell us about source devices
        if (!(o = pa_context_get_source_info_list(c,
                                            sourcelist_cb,
                                            NULL
                                                  ))) {
            show_error(_("pa_context_subscribe() failed"));
            return;
        }
        pa_operation_unref(o);


        // set up a callback to tell us about sink devices
        if (!(o = pa_context_get_sink_info_list(c,
                                            sinklist_cb,
                                            NULL
                                                  ))) {
            show_error(_("pa_context_subscribe() failed"));
            return;
        }
        pa_operation_unref(o);


        break;
    }


    case PA_CONTEXT_FAILED:
    case PA_CONTEXT_TERMINATED:
    default:
        return;
    }
}


int main(int argc, char *argv[]) {

    // Define our pulse audio loop and connection variables
    pa_mainloop *pa_ml;
    pa_mainloop_api *pa_mlapi;


    // Create a mainloop API and connection to the default server
    pa_ml = pa_mainloop_new();
    pa_mlapi = pa_mainloop_get_api(pa_ml);
    context = pa_context_new(pa_mlapi, "Device list");


    // This function connects to the pulse server
    pa_context_connect(context, NULL, 0, NULL);


    // This function defines a callback so the server will tell us its state.
    pa_context_set_state_callback(context, context_state_cb, NULL);


    if (pa_mainloop_run(pa_ml, &ret) < 0) {
        printf("pa_mainloop_run() failed.");
        exit(1);
    }
}

On my laptop, this gives the following (elided):

Source: name alsa_output.pci-0000_01_00.1.hdmi-stereo.monitor, description Monitor of HDMI Audio stub Digital Stereo (HDMI)
  Properties are:
   key: device.description, value: Monitor of HDMI Audio stub Digital Stereo (HDMI)
   key: device.class, value: monitor
   key: alsa.card, value: 1
   key: alsa.card_name, value: HDA NVidia
   key: alsa.long_card_name, value: HDA NVidia at 0xe5080000 irq 17
   key: alsa.driver_name, value: snd_hda_intel
   key: device.bus_path, value: pci-0000:01:00.1
   key: sysfs.path, value: /devices/pci0000:00/0000:00:01.0/0000:01:00.1/sound/card1
   key: device.bus, value: pci
   key: device.vendor.id, value: 10de
   key: device.vendor.name, value: nVidia Corporation
   key: device.product.id, value: 0e08
   key: device.product.name, value: HDMI Audio stub
   key: device.string, value: 1
   key: module-udev-detect.discovered, value: 1
   key: device.icon_name, value: audio-card-pci
Source: name alsa_output.pci-0000_00_1b.0.analog-stereo.monitor, description Monitor of Internal Audio Analog Stereo
  Properties are:
   ...
Source: name alsa_input.pci-0000_00_1b.0.analog-stereo, description Internal Audio Analog Stereo
  Properties are:
  ...
Source: name alsa_output.usb-Creative_Technology_Ltd_SB_X-Fi_Surround_5.1_Pro_000003d0-00-Pro.analog-stereo.monitor, description Monitor of SB X-Fi Surround 5.1 Pro Analog Stereo
  Properties are:
  ...
Source: name alsa_input.usb-Creative_Technology_Ltd_SB_X-Fi_Surround_5.1_Pro_000003d0-00-Pro.analog-stereo, description SB X-Fi Surround 5.1 Pro Analog Stereo
  Properties are:
  ...
**No more sources
Sink: name alsa_output.pci-0000_01_00.1.hdmi-stereo, description HDMI Audio stub Digital Stereo (HDMI)
  Properties are:
   key: alsa.resolution_bits, value: 16
   key: device.api, value: alsa
   key: device.class, value: sound
   key: alsa.class, value: generic
   key: alsa.subclass, value: generic-mix
   key: alsa.name, value: HDMI 0
   key: alsa.id, value: HDMI 0
   key: alsa.subdevice, value: 0
   key: alsa.subdevice_name, value: subdevice #0
   key: alsa.device, value: 3
   key: alsa.card, value: 1
   key: alsa.card_name, value: HDA NVidia
   key: alsa.long_card_name, value: HDA NVidia at 0xe5080000 irq 17
   key: alsa.driver_name, value: snd_hda_intel
   key: device.bus_path, value: pci-0000:01:00.1
   key: sysfs.path, value: /devices/pci0000:00/0000:00:01.0/0000:01:00.1/sound/card1
   key: device.bus, value: pci
   key: device.vendor.id, value: 10de
   key: device.vendor.name, value: nVidia Corporation
   key: device.product.id, value: 0e08
   key: device.product.name, value: HDMI Audio stub
   key: device.string, value: hdmi:1
   key: device.buffering.buffer_size, value: 352768
   key: device.buffering.fragment_size, value: 176384
   key: device.access_mode, value: mmap+timer
   key: device.profile.name, value: hdmi-stereo
   key: device.profile.description, value: Digital Stereo (HDMI)
   key: device.description, value: HDMI Audio stub Digital Stereo (HDMI)
   key: alsa.mixer_name, value: Nvidia GPU 1c HDMI/DP
   key: alsa.components, value: HDA:10de001c,10281494,00100100
   key: module-udev-detect.discovered, value: 1
   key: device.icon_name, value: audio-card-pci
Sink: name alsa_output.pci-0000_00_1b.0.analog-stereo, description Internal Audio Analog Stereo
  Properties are:
  ...
Sink: name alsa_output.usb-Creative_Technology_Ltd_SB_X-Fi_Surround_5.1_Pro_000003d0-00-Pro.analog-stereo, description SB X-Fi Surround 5.1 Pro Analog Stereo
  Properties are:
  ...
**No more sinks

An alternative program with the same effect is PulseAudio: An Async Example To Get Device Lists ( www.ypass.net/blog/2009/10/pulseaudio-an-async-example-to-get-device-lists ) by Igor Brezac and Eric Connell. It doesn’t follow quite as complex a route as the previous, as it only queries the server for its devices. However, it uses its own state machine to track where in the callback process it is!

Monitoring Ongoing Changes: New Sources and Sinks

The previous program listed the source and sink devices registered with PulseAudio at the time a connection to the server was established. However, when a new device is connected or an existing device is disconnected, PulseAudio registers a changes in the context, and this can also be monitored by callbacks.

The key to doing this is to subscribe to context changes with pa_context_subscribe. This takes a context, a mask of subscription events, and user data. Possible values of the mask are described at http://freedesktop.org/software/pulseaudio/doxygen/def_8h.html#ad4e7f11f879e8c77ae5289145ecf6947 and include PA_SUBSCRIPTION_MASK_SINK for changes in sinks and PA_SUBSCRIPTION_MASK_SINK_INPUT for sink input events.

Setting the callback function to monitor these changes is a bit odd. The function pa_context_subscribe takes a callback function of type pa_context_success_cb, but this doesn’t contain information about what caused the callback. Instead, it is better to first call pa_context_set_subscribe_callback. This takes a callback function of type pa_context_subscribe_cb_t, which does get passed such information. Then use NULL for the callback in pa_context_subscribe!

Within a pa_context_subscribe_cb_t subscription callback, the cause of the callback can be examined and appropriate code called. If a new subscription to a sink is found, then information about the sink can be found with pa_context_get_sink_info_by_index, which takes another callback! After chasing through all these callbacks, you can eventually get information about new devices.

Note that the callback function used by pa_context_get_sink_info_list and the callback function used by pa_context_get_sink_info_by_index are the same. The callback is called once per sink device regardless of whether it is a singleton or one of a list of devices.

A program to list devices on connection and also to list changes as devices are connected or disconnected is palist_devices_ongoing.c:
/**
 * palist_clients.c
 * Jan Newmarch
 */


/***
    This file is based on pacat.c and pavuctl.c,  part of PulseAudio.


    pacat.c:
    Copyright 2004-2006 Lennart Poettering
    Copyright 2006 Pierre Ossman <[email protected]> for Cendio AB


    PulseAudio is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published
    by the Free Software Foundation; either version 2.1 of the License,
    or (at your option) any later version.


    PulseAudio 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 Lesser General Public License
    along with PulseAudio; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
    USA.
***/


#include <stdio.h>
#include <string.h>
#include <pulse/pulseaudio.h>


#define _(x) x

int ret;

pa_context *context;

void show_error(char *s) {
    fprintf(stderr, "%s ", s);
}


void print_properties(pa_proplist *props) {
    void *state = NULL;


    printf("  Properties are: ");
    while (1) {
        char *key;
        if ((key = pa_proplist_iterate(props, &state)) == NULL) {
            return;
        }
        char *value = pa_proplist_gets(props, key);
        printf("   key: %s, value: %s ", key, value);
    }
}


/**
 * print information about a sink
 */
void sink_cb(pa_context *c, const pa_sink_info *i, int eol, void *userdata) {


    // If eol is set to a positive number, you're at the end of the list
    if (eol > 0) {
        return;
    }


    printf("Sink: name %s, description %s ", i->name, i->description);
    // print_properties(i->proplist);
}


/**
 * print information about a source
 */
void source_cb(pa_context *c, const pa_source_info *i, int eol, void *userdata) {
    if (eol > 0) {
        return;
    }


    printf("Source: name %s, description %s ", i->name, i->description);
    // print_properties(i->proplist);
}


void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) {

    switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {

    case PA_SUBSCRIPTION_EVENT_SINK:
        if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
            printf("Removing sink index %d ", index);
        else {
            pa_operation *o;
            if (!(o = pa_context_get_sink_info_by_index(c, index, sink_cb, NULL))) {
                show_error(_("pa_context_get_sink_info_by_index() failed"));
                return;
            }
            pa_operation_unref(o);
        }
        break;


    case PA_SUBSCRIPTION_EVENT_SOURCE:
        if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
            printf("Removing source index %d ", index);
        else {
            pa_operation *o;
            if (!(o = pa_context_get_source_info_by_index(c, index, source_cb, NULL))) {
                show_error(_("pa_context_get_source_info_by_index() failed"));
                return;
            }
            pa_operation_unref(o);
        }
        break;
    }
}


void context_state_cb(pa_context *c, void *userdata) {

    switch (pa_context_get_state(c)) {
    case PA_CONTEXT_UNCONNECTED:
    case PA_CONTEXT_CONNECTING:
    case PA_CONTEXT_AUTHORIZING:
    case PA_CONTEXT_SETTING_NAME:
        break;


    case PA_CONTEXT_READY: {
        pa_operation *o;


        if (!(o = pa_context_get_source_info_list(c,
                                                  source_cb,
                                                  NULL
                                                  ))) {
            show_error(_("pa_context_subscribe() failed"));
            return;
        }
        pa_operation_unref(o);


        if (!(o = pa_context_get_sink_info_list(c,
                                                sink_cb,
                                                NULL
                                                ))) {
            show_error(_("pa_context_subscribe() failed"));
            return;
        }
        pa_operation_unref(o);


        pa_context_set_subscribe_callback(c, subscribe_cb, NULL);

        if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
                                       (PA_SUBSCRIPTION_MASK_SINK|
                                        PA_SUBSCRIPTION_MASK_SOURCE), NULL, NULL))) {
            show_error(_("pa_context_subscribe() failed"));
            return;
        }
        pa_operation_unref(o);


        break;
    }


    case PA_CONTEXT_FAILED:
    case PA_CONTEXT_TERMINATED:
    default:
        return;
    }
}


int main(int argc, char *argv[]) {

    // Define our pulse audio loop and connection variables
    pa_mainloop *pa_ml;
    pa_mainloop_api *pa_mlapi;
    pa_operation *pa_op;
    pa_time_event *time_event;


    // Create a mainloop API and connection to the default server
    pa_ml = pa_mainloop_new();
    pa_mlapi = pa_mainloop_get_api(pa_ml);
    context = pa_context_new(pa_mlapi, "Device list");


    // This function connects to the pulse server
    pa_context_connect(context, NULL, 0, NULL);


    // This function defines a callback so the server will tell us its state.
    pa_context_set_state_callback(context, context_state_cb, NULL);


    if (pa_mainloop_run(pa_ml, &ret) < 0) {
        printf("pa_mainloop_run() failed.");
        exit(1);
    }
}

Record a Stream

If you download the source for PulseAudio from FreeDesktop.org ( www.freedesktop.org/wiki/Software/PulseAudio/Download ), you will find a program called pacat.c in the utils directory. This program uses some of the private APIs and will not compile using the public libraries. It also has all the bells and whistles that you would expect from a production program. I've taken this and stripped out the complexities so that you can find your way into this API. The file is parec.c.

/**

 * parec.c
 * Jan Newmarch
 */


/***
  This file is based on pacat.c,  part of PulseAudio.


  pacat.c:
  Copyright 2004-2006 Lennart Poettering
  Copyright 2006 Pierre Ossman <[email protected]> for Cendio AB


  PulseAudio is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as published
  by the Free Software Foundation; either version 2.1 of the License,
  or (at your option) any later version.


  PulseAudio 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 Lesser General Public License
  along with PulseAudio; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  USA.
***/


#include <stdio.h>
#include <string.h>
#include <pulse/pulseaudio.h>


#define CLEAR_LINE " "
#define _(x) x


// From pulsecore/macro.h
#define pa_memzero(x,l) (memset((x), 0, (l)))
#define pa_zero(x) (pa_memzero(&(x), sizeof(x)))


int fdout;
char *fname = "tmp.s16";


int verbose = 1;
int ret;


pa_context *context;

static pa_sample_spec sample_spec = {
  .format = PA_SAMPLE_S16LE,
  .rate = 44100,
  .channels = 2
};


static pa_stream *stream = NULL;

/* This is my builtin card. Use paman to find yours
   or set it to NULL to get the default device
*/
static char *device = "alsa_input.pci-0000_00_1b.0.analog-stereo";


static pa_stream_flags_t flags = 0;

void stream_state_callback(pa_stream *s, void *userdata) {
  assert(s);


  switch (pa_stream_get_state(s)) {
  case PA_STREAM_CREATING:
    // The stream has been created, so
    // let's open a file to record to
    printf("Creating stream ");
    fdout = creat(fname,  0711);
    break;


  case PA_STREAM_TERMINATED:
    close(fdout);
    break;


  case PA_STREAM_READY:

    // Just for info: no functionality in this branch
    if (verbose) {
      const pa_buffer_attr *a;
      char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX];


      printf("Stream successfully created.");

      if (!(a = pa_stream_get_buffer_attr(s)))
        printf("pa_stream_get_buffer_attr() failed: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
      else {
        printf("Buffer metrics: maxlength=%u, fragsize=%u", a->maxlength, a->fragsize);


      }

      printf("Connected to device %s (%u, %ssuspended).",
             pa_stream_get_device_name(s),
             pa_stream_get_device_index(s),
             pa_stream_is_suspended(s) ? "" : "not ");
    }


    break;

  case PA_STREAM_FAILED:
  default:
    printf("Stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
    exit(1);
  }
}


/*********** Stream callbacks **************/

/* This is called whenever new data is available */
static void stream_read_callback(pa_stream *s, size_t length, void *userdata) {


  assert(s);
  assert(length > 0);


  // Copy the data from the server out to a file
  fprintf(stderr, "Can read %d ", length);


  while (pa_stream_readable_size(s) > 0) {
    const void *data;
    size_t length;


    // peek actually creates and fills the data vbl
    if (pa_stream_peek(s, &data, &length) < 0) {
      fprintf(stderr, "Read failed ");
      exit(1);
      return;
    }
    fprintf(stderr, "Writing %d ", length);
    write(fdout, data, length);


    // swallow the data peeked at before
    pa_stream_drop(s);
  }
}


// This callback gets called when our context changes state.  We really only
// care about when it's ready or if it has failed
void state_cb(pa_context *c, void *userdata) {
  pa_context_state_t state;
  int *pa_ready = userdata;


  printf("State changed ");
  state = pa_context_get_state(c);
  switch  (state) {
    // There are just here for reference
  case PA_CONTEXT_UNCONNECTED:
  case PA_CONTEXT_CONNECTING:
  case PA_CONTEXT_AUTHORIZING:
  case PA_CONTEXT_SETTING_NAME:
  default:
    break;
  case PA_CONTEXT_FAILED:
  case PA_CONTEXT_TERMINATED:
    *pa_ready = 2;
    break;
  case PA_CONTEXT_READY: {
    pa_buffer_attr buffer_attr;


    if (verbose)
      printf("Connection established.%s ", CLEAR_LINE);


    if (!(stream = pa_stream_new(c, "JanCapture", &sample_spec, NULL))) {
      printf("pa_stream_new() failed: %s", pa_strerror(pa_context_errno(c)));
      exit(1);
    }


    // Watch for changes in the stream state to create the output file
    pa_stream_set_state_callback(stream, stream_state_callback, NULL);


    // Watch for changes in the stream's read state to write to the output file
    pa_stream_set_read_callback(stream, stream_read_callback, NULL);


    // Set properties of the record buffer
    pa_zero(buffer_attr);
    buffer_attr.maxlength = (uint32_t) -1;
    buffer_attr.prebuf = (uint32_t) -1;
    buffer_attr.fragsize = buffer_attr.tlength = (uint32_t) -1;
    buffer_attr.minreq = (uint32_t) -1;


    // and start recording
    if (pa_stream_connect_record(stream, device, &buffer_attr, flags) < 0) {
      printf("pa_stream_connect_record() failed: %s", pa_strerror(pa_context_errno(c)));
      exit(1);
    }
  }


    break;
  }
}


int main(int argc, char *argv[]) {

  // Define our pulse audio loop and connection variables
  pa_mainloop *pa_ml;
  pa_mainloop_api *pa_mlapi;
  pa_operation *pa_op;
  pa_time_event *time_event;


  // Create a mainloop API and connection to the default server
  pa_ml = pa_mainloop_new();
  pa_mlapi = pa_mainloop_get_api(pa_ml);
  context = pa_context_new(pa_mlapi, "test");


  // This function connects to the pulse server
  pa_context_connect(context, NULL, 0, NULL);


  // This function defines a callback so the server will tell us its state.
  pa_context_set_state_callback(context, state_cb, NULL);


  if (pa_mainloop_run(pa_ml, &ret) < 0) {
    printf("pa_mainloop_run() failed.");
    exit(1);
  }
}

Play a File

Recording an input stream is done within a stream read callback with the call pa_stream_peek. Similarly, playing an output stream is done with a stream write callback with the call pa_stream_write.

In the following program, the callback is set within the PA_CONTEXT_READY branch of the context state change callback. The stream write callback is passed the number of bytes the consuming stream is prepared to receive, so read that number of bytes from the file and write them to the stream.

Care has to be taken at the end of file. There may be unplayed material in PulseAudio’s output buffers. This needs to be drained before the program can exit. This is done by the function pa_stream_drain. On end-of-file, first set the stream write callback to null so that the output stream doesn’t keep calling for more input and then drain the stream. A stream drain complete callback will be called on completion of this, so the program can then exit (or do something else).

In this program, I include many more callbacks than in earlier ones to show the range of features that can be monitored.

The program is pacat2.c.

/**
 * pacat2.c
 * Jan Newmarch
 */


/***
  This file is based on pacat.c,  part of PulseAudio.


  pacat.c:
  Copyright 2004-2006 Lennart Poettering
  Copyright 2006 Pierre Ossman <[email protected]> for Cendio AB


  PulseAudio is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as published
  by the Free Software Foundation; either version 2.1 of the License,
  or (at your option) any later version.


  PulseAudio 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 Lesser General Public License
  along with PulseAudio; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  USA.
***/


#include <stdio.h>
#include <string.h>
#include <pulse/pulseaudio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>


// ???
#define CLEAR_LINE " "


// From pulsecore/macro.h JN
#define pa_memzero(x,l) (memset((x), 0, (l)))
#define pa_zero(x) (pa_memzero(&(x), sizeof(x)))


int verbose = 1;
int ret;


static pa_volume_t volume = PA_VOLUME_NORM;
static int volume_is_set = 0;


static int fdin;

static pa_sample_spec sample_spec = {
  .format = PA_SAMPLE_S16LE,
  .rate = 44100,
  .channels = 2
};


static pa_stream *stream = NULL;
static pa_channel_map channel_map;
static pa_proplist *proplist = NULL;


// Define our pulse audio loop and connection variables
static pa_mainloop *mainloop;
static pa_mainloop_api *mainloop_api;
static pa_operation *pa_op;
static pa_context *context = NULL;


static void *buffer = NULL;
static size_t buffer_length = 0, buffer_index = 0;


static pa_io_event* stdio_event = NULL;

// Get device name from e.g. paman
//static char *device = "alsa_output.pci-0000_00_1b.0.analog-stereo";
// Use default device
static char *device = NULL;


static pa_stream_flags_t flags = 0;

static size_t latency = 0, process_time = 0;
static int32_t latency_msec = 1, process_time_msec = 0;


static int raw = 1;

/* Connection draining complete */
static void context_drain_complete(pa_context*c, void *userdata) {
  pa_context_disconnect(c);
}


static void stream_drain_complete(pa_stream*s, int success, void *userdata) {
  pa_operation *o = NULL;


  if (!success) {
    printf("Failed to drain stream: %s", pa_strerror(pa_context_errno(context)));
    exit(1);
  }


  if (verbose)
    printf("Playback stream drained.");


  pa_stream_disconnect(stream);
  pa_stream_unref(stream);
  stream = NULL;


  if (!(o = pa_context_drain(context, context_drain_complete, NULL)))
    pa_context_disconnect(context);
  else {
    pa_operation_unref(o);
    if (verbose)
      printf("Draining connection to server.");
  }
}


/* Start draining */
static void start_drain(void) {
  printf("Draining ");
  if (stream) {
    pa_operation *o;


    pa_stream_set_write_callback(stream, NULL, NULL);

    if (!(o = pa_stream_drain(stream, stream_drain_complete, NULL))) {
      //printf("pa_stream_drain(): %s", pa_strerror(pa_context_errno(context)));
      exit(1);
      return;
    }


    pa_operation_unref(o);
  } else
    exit(0);
}


/* Write some data to the stream */
static void do_stream_write(size_t length) {
  size_t l;
  assert(length);


  printf("do stream write: Writing %d to stream ", length);

  if (!buffer || !buffer_length) {
    buffer = pa_xmalloc(length);
    buffer_length = length;
    buffer_index = 0;
    //printf("  return without writing ");
    //return;


  }

  while (buffer_length > 0) {
    l = read(fdin, buffer + buffer_index, buffer_length);
    if (l <= 0) {
      start_drain();
      return;
    }
    if (pa_stream_write(stream, (uint8_t*) buffer + buffer_index, l, NULL, 0, PA_SEEK_RELATIVE) < 0) {
      printf("pa_stream_write() failed: %s", pa_strerror(pa_context_errno(context)));
      exit(1);
      return;
    }
    buffer_length -= l;
    buffer_index += l;


    if (!buffer_length) {
      pa_xfree(buffer);
      buffer = NULL;
      buffer_index = buffer_length = 0;
    }
  }
}


void stream_state_callback(pa_stream *s, void *userdata) {
  assert(s);


  switch (pa_stream_get_state(s)) {
  case PA_STREAM_CREATING:
    break;
  case PA_STREAM_TERMINATED:
    break;


  case PA_STREAM_READY:

    if (verbose) {
      const pa_buffer_attr *a;
      char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX];


      printf("Stream successfully created. ");

      if (!(a = pa_stream_get_buffer_attr(s)))
        printf("pa_stream_get_buffer_attr() failed: %s ", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
      else {
        printf("Buffer metrics: maxlength=%u, fragsize=%u ", a->maxlength, a->fragsize);


      }
      /*
        printf("Using sample spec '%s', channel map '%s'.",
        pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(s)),
        pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(s)));
      */


      printf("Connected to device %s (%u, %ssuspended). ",
             pa_stream_get_device_name(s),
             pa_stream_get_device_index(s),
             pa_stream_is_suspended(s) ? "" : "not ");
    }


    break;

  case PA_STREAM_FAILED:
  default:
    printf("Stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
    exit(1); //quit(1);
  }
}


/*********** Stream callbacks **************/

static void stream_success(pa_stream *s, int succes, void *userdata) {
  printf("Succeded ");
}


static void stream_suspended_callback(pa_stream *s, void *userdata) {
  assert(s);


  if (verbose) {
    if (pa_stream_is_suspended(s))
      fprintf(stderr, "Stream device suspended.%s ", CLEAR_LINE);
    else
      fprintf(stderr, "Stream device resumed.%s ", CLEAR_LINE);
  }
}


static void stream_underflow_callback(pa_stream *s, void *userdata) {
  assert(s);


  if (verbose)
    fprintf(stderr, "Stream underrun.%s ",  CLEAR_LINE);
}


static void stream_overflow_callback(pa_stream *s, void *userdata) {
  assert(s);


  if (verbose)
    fprintf(stderr, "Stream overrun.%s ", CLEAR_LINE);
}


static void stream_started_callback(pa_stream *s, void *userdata) {
  assert(s);


  if (verbose)
    fprintf(stderr, "Stream started.%s ", CLEAR_LINE);
}


static void stream_moved_callback(pa_stream *s, void *userdata) {
  assert(s);


  if (verbose)
    fprintf(stderr, "Stream moved to device %s (%u, %ssuspended).%s ", pa_stream_get_device_name(s), pa_stream_get_device_index(s), pa_stream_is_suspended(s) ? "" : "not ",  CLEAR_LINE);
}


static void stream_buffer_attr_callback(pa_stream *s, void *userdata) {
  assert(s);


  if (verbose)
    fprintf(stderr, "Stream buffer attributes changed.%s ",  CLEAR_LINE);
}


static void stream_event_callback(pa_stream *s, const char *name, pa_proplist *pl, void *userdata) {
  char *t;


  assert(s);
  assert(name);
  assert(pl);


  t = pa_proplist_to_string_sep(pl, ", ");
  fprintf(stderr, "Got event '%s', properties '%s' ", name, t);
  pa_xfree(t);
}


/* This is called whenever new data may be written to the stream */
static void stream_write_callback(pa_stream *s, size_t length, void *userdata) {
  //assert(s);
  //assert(length > 0);


  printf("Stream write callback: Ready to write %d bytes ", length);

  printf("  do stream write from stream write callback ");
  do_stream_write(length);


 }

// This callback gets called when our context changes state.  We really only
// care about when it's ready or if it has failed
void state_cb(pa_context *c, void *userdata) {
  pa_context_state_t state;
  int *pa_ready = userdata;


  printf("State changed ");
  state = pa_context_get_state(c);
  switch  (state) {
    // There are just here for reference
  case PA_CONTEXT_UNCONNECTED:
  case PA_CONTEXT_CONNECTING:
  case PA_CONTEXT_AUTHORIZING:
  case PA_CONTEXT_SETTING_NAME:
  default:
    break;
  case PA_CONTEXT_FAILED:
  case PA_CONTEXT_TERMINATED:
    *pa_ready = 2;
    break;
  case PA_CONTEXT_READY: {
    pa_buffer_attr buffer_attr;


    if (verbose)
      printf("Connection established.%s ", CLEAR_LINE);


    if (!(stream = pa_stream_new(c, "JanPlayback", &sample_spec, NULL))) {
      printf("pa_stream_new() failed: %s", pa_strerror(pa_context_errno(c)));
      exit(1); // goto fail;
    }


    pa_stream_set_state_callback(stream, stream_state_callback, NULL);

    pa_stream_set_write_callback(stream, stream_write_callback, NULL);

    //pa_stream_set_read_callback(stream, stream_read_callback, NULL);

    pa_stream_set_suspended_callback(stream, stream_suspended_callback, NULL);
    pa_stream_set_moved_callback(stream, stream_moved_callback, NULL);
    pa_stream_set_underflow_callback(stream, stream_underflow_callback, NULL);
    pa_stream_set_overflow_callback(stream, stream_overflow_callback, NULL);


    pa_stream_set_started_callback(stream, stream_started_callback, NULL);

    pa_stream_set_event_callback(stream, stream_event_callback, NULL);
    pa_stream_set_buffer_attr_callback(stream, stream_buffer_attr_callback, NULL);


    pa_zero(buffer_attr);
    buffer_attr.maxlength = (uint32_t) -1;
    buffer_attr.prebuf = (uint32_t) -1;


    pa_cvolume cv;

    if (pa_stream_connect_playback(stream, NULL, &buffer_attr, flags,
                                   NULL,
                                   NULL) < 0) {
      printf("pa_stream_connect_playback() failed: %s", pa_strerror(pa_context_errno(c)));
      exit(1); //goto fail;
    } else {
      printf("Set playback callback ");
    }


    pa_stream_trigger(stream, stream_success, NULL);
  }


    break;
  }
}


int main(int argc, char *argv[]) {

  struct stat st;
  off_t size;
  ssize_t nread;


  // We'll need these state variables to keep track of our requests
  int state = 0;
  int pa_ready = 0;


  if (argc != 2) {
    fprintf(stderr, "Usage: %s file ", argv[0]);
    exit(1);
  }
  // slurp the whole file into buffer
  if ((fdin = open(argv[1],  O_RDONLY)) == -1) {
    perror("open");
    exit(1);
  }


  // Create a mainloop API and connection to the default server
  mainloop = pa_mainloop_new();
  mainloop_api = pa_mainloop_get_api(mainloop);
  context = pa_context_new(mainloop_api, "test");


  // This function connects to the pulse server
  pa_context_connect(context, NULL, 0, NULL);
  printf("Connecting ");


  // This function defines a callback so the server will tell us it's state.
  // Our callback will wait for the state to be ready.  The callback will
  // modify the variable to 1 so we know when we have a connection and it's
  // ready.
  // If there's an error, the callback will set pa_ready to 2
  pa_context_set_state_callback(context, state_cb, &pa_ready);


  if (pa_mainloop_run(mainloop, &ret) < 0) {
    printf("pa_mainloop_run() failed.");
    exit(1); // goto quit
  }


}

With the latency set to the default, the number of bytes that can be written on each callback is 65,470 bytes. This gives a minimum latency of 65,470 / 44,100s, or about 1500ms. With the latency and process time both set to 1ms, the buffer size is about 1440 bytes, for a latency of 32ms.

Play a File Using I/O Callbacks

Writing a file to an output stream is simple: read from a file into a buffer and keep emptying the buffer by writing to the stream. Reading from a file is straightforward: use the standard Unix read function. You request a read of a number of bytes, and the read function returns the number of bytes actually read. This was discussed in the previous section.

The program in the PulseAudio distribution uses a more complex system. It uses I/O-ready callbacks to pass some handling to an I/O callback. This makes use of two functions.

  • pa_stream_writable_size tells how many bytes can be written to the stream.

  • pa_stream_write writes a number of bytes to a stream.

The logic becomes as follows: fill a buffer by reading from the file, and at the same time write as many bytes as possible from the buffer to the stream, up to the limit of the buffer size or however many bytes the stream can take, whichever is smaller.

In PulseAudio this is done asynchronously, using callback functions. The two relevant functions are as follows:

  • The function pa_stream_set_write_callback() registers a callback that will be called whenever the stream is ready to be written to. Registering the callback looks like this:

    pa_stream_set_write_callback(stream, stream_write_callback, NULL)

    The callback is passed the stream to write to (s) and the number of bytes that can be written (length).

    void stream_write_callback(pa_stream *s, size_t length, void *userdata)
  • A callback to read from files is registered by one of the functions kept in the mainloop_api table. The registering function is io_new and is passed a Unix file descriptor for the file and the callback function. Registering the callback looks like this:

    mainloop_api->io_new(mainloop_api,
                         fdin,
                         PA_IO_EVENT_INPUT,
                         stdin_callback, NULL))

    The callback is passed the file descriptor (fd) to read from.

    void stdin_callback(pa_mainloop_api *mainloop_api, pa_io_event *stdio_event,
                        int fd, pa_io_event_flags_t f, void *userdata)

    Note The PulseAudio code does a dup2 from the source file’s descriptor to STDIN_FILENO, which matches the name of the function. I can’t see the point of that, and their code uses fd anyway.

When should these callbacks be registered? The stream write callback can be registered at any time after the stream has been created, which is done by pa_stream_new. For the stdin callback, I could only get it to work properly by registering it once the stream was ready, that is, in the PA_STREAM_READY branch of the stream state callback function.

So, after all that, what is the logic of the program?

  • In the stdin callback:

    • If the buffer has stuff in it, then just return. There’s no point in adding any more.

    • If the buffer is empty, then query the stream to see how much can be written to it.

    • If the stream says no more, then just read something into the buffer and return.

    • If the stream can be written to, then read from the file into the buffer and write it to the stream.

  • In the stream write callback:

    • If the buffer is nonempty, write its contents to the stream.

The program to play from a file currently looks like pacat.c.

/***
  This file is based on pacat.c,  part of PulseAudio .


  pacat.c:
  Copyright 2004-2006 Lennart Poettering
  Copyright 2006 Pierre Ossman <[email protected]> for Cendio AB


  PulseAudio is free software; you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as published
  by the Free Software Foundation; either version 2.1 of the License,
  or (at your option) any later version.


  PulseAudio 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 Lesser General Public License
  along with PulseAudio; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  USA.
***/


#include <stdio.h>
#include <string.h>
#include <pulse/pulseaudio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>


#define CLEAR_LINE " "

// From pulsecore/macro.h JN
#define pa_memzero(x,l) (memset((x), 0, (l)))
#define pa_zero(x) (pa_memzero(&(x), sizeof(x)))


int verbose = 1;
int ret;


static pa_volume_t volume = PA_VOLUME_NORM;
static int volume_is_set = 0;


static int fdin; .

static pa_sample_spec sample_spec = {
  .format = PA_SAMPLE_S16LE,
  .rate = 44100,
  .channels = 2
};


static pa_stream *stream = NULL;
static pa_channel_map channel_map;
static pa_proplist *proplist = NULL;


// Define our pulse audio loop and connection variables
static pa_mainloop *mainloop;
static pa_mainloop_api *mainloop_api;
static pa_operation *pa_op;
static pa_context *context = NULL;


static void *buffer = NULL;
static size_t buffer_length = 0, buffer_index = 0;


static pa_io_event* stdio_event = NULL;

static char *device = "alsa_output.pci-0000_00_1b.0.analog-stereo";

static pa_stream_flags_t flags = 0;

static size_t latency = 0, process_time = 0;
static int32_t latency_msec = 0, process_time_msec = 0;


static int raw = 1;

/* Write some data to the stream */
static void do_stream_write(size_t length) {
  size_t l;
  assert(length);


  printf("do stream write: Writing %d to stream ", length);

  if (!buffer || !buffer_length) {
    printf("  return without writing ");
    return; .
  }


  l = length;
  if (l > buffer_length)
    l = buffer_length;
  printf("  writing %d ", l);
  if (pa_stream_write(stream, (uint8_t*) buffer + buffer_index, l, NULL, 0, PA_SEEK_RELATIVE) < 0) {
    printf("pa_stream_write() failed: %s", pa_strerror(pa_context_errno(context)));
    exit(1);
    return;
  }


  buffer_length -= l;
  buffer_index += l;


  if (!buffer_length) {
    pa_xfree(buffer);
    buffer = NULL;
    buffer_index = buffer_length = 0;
  }


}

/* Connection draining complete */
static void context_drain_complete(pa_context*c, void *userdata) {
  pa_context_disconnect(c);
}


static void stream_drain_complete(pa_stream*s, int success, void *userdata) {
  pa_operation *o = NULL;


  if (!success) {
    printf("Failed to drain stream: %s", pa_strerror(pa_context_errno(context)));
    exit(1);
  }


  if (verbose)
    printf("Playback stream drained.");


  pa_stream_disconnect(stream);
  pa_stream_unref(stream);
  stream = NULL;


  if (!(o = pa_context_drain(context, context_drain_complete, NULL)))
    pa_context_disconnect(context); .
  else {
    pa_operation_unref(o);
    if (verbose)
      printf("Draining connection to server.");
  }
}


/* Start draining */
static void start_drain(void) {
  printf("Draining ");
  if (stream) {
    pa_operation *o;


    pa_stream_set_write_callback(stream, NULL, NULL);

    if (!(o = pa_stream_drain(stream, stream_drain_complete, NULL))) {
      //printf("pa_stream_drain(): %s", pa_strerror(pa_context_errno(context)));
      exit(1);
      return;
    }


    pa_operation_unref(o);
  } else
    exit(0);
}


/* New data on STDIN **/
static void stdin_callback(pa_mainloop_api *mainloop_api, pa_io_event *stdio_event, int fd, pa_io_event_flags_t f, void *userdata) {
  size_t l, w = 0;
  ssize_t r;


  printf("In stdin callback ");
  //pa_assert(a == mainloop_api);
  // pa_assert(e);
  // pa_assert(stdio_event == e);


  if (buffer) {
    mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL);
    printf("  Buffer isn't null "); .
    return;
  }


  if (!stream || pa_stream_get_state(stream) != PA_STREAM_READY || !(l = w = pa_stream_writable_size(stream)))
    l = 4096;


  buffer = pa_xmalloc(l);

  if ((r = read(fd, buffer, l)) <= 0) {
    if (r == 0) {
      if (verbose)
        printf("Got EOF. ");


      start_drain();

    } else {
      printf("read() failed: %s ", strerror(errno));
      exit(1);
    }


    mainloop_api->io_free(stdio_event);
    stdio_event = NULL;
    return;
  }
  printf("  Read %d ", r);


  buffer_length = (uint32_t) r;
  buffer_index = 0;


  if (w) {
    printf("  do stream write from stdin callback ");
    do_stream_write(w);
  }
}


void stream_state_callback(pa_stream *s, void *userdata) {
  assert(s);


  switch (pa_stream_get_state(s)) {
  case PA_STREAM_CREATING: .
    break;
  case PA_STREAM_TERMINATED:
    break;


  case PA_STREAM_READY:

    if (verbose) {
      const pa_buffer_attr *a;
      char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX];


      printf("Stream successfully created. ");

      if (!(a = pa_stream_get_buffer_attr(s)))
        printf("pa_stream_get_buffer_attr() failed: %s ", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
      else {
        printf("Buffer metrics: maxlength=%u, fragsize=%u ", a->maxlength, a->fragsize);


      }
      /*
        printf("Using sample spec '%s', channel map '%s'.",
        pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(s)),
        pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(s))); .
      */


      printf("Connected to device %s (%u, %ssuspended). ",
             pa_stream_get_device_name(s),
             pa_stream_get_device_index(s),
             pa_stream_is_suspended(s) ? "" : "not ");
    }


    // TRY HERE???

    if (!(stdio_event = mainloop_api->io_new(mainloop_api,
                                             fdin, // STDIN_FILENO,
                                             PA_IO_EVENT_INPUT,
                                             stdin_callback, NULL))) {
      printf("io_new() failed.");
      exit(1);
    }


    break;

  case PA_STREAM_FAILED:
  default:
    printf("Stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
    exit(1); //quit(1);
  }
}


/*********** Stream callbacks **************/

static void stream_read_callback(pa_stream *s, size_t length, void *userdata) {
  printf("Raedy to read ");
}


static void stream_success(pa_stream *s, int succes, void *userdata) {
  printf("Succeded ") .;
}


static void stream_suspended_callback(pa_stream *s, void *userdata) {
  assert(s);


  if (verbose) {
    if (pa_stream_is_suspended(s))
      fprintf(stderr, "Stream device suspended.%s ", CLEAR_LINE);
    else
      fprintf(stderr, "Stream device resumed.%s ", CLEAR_LINE);
  }
}


static void stream_underflow_callback(pa_stream *s, void *userdata) {
  assert(s);


  if (verbose)
    fprintf(stderr, "Stream underrun.%s ",  CLEAR_LINE);
}


static void stream_overflow_callback(pa_stream *s, void *userdata) {
  assert(s);


  if (verbose)
    fprintf(stderr, "Stream overrun.%s ", CLEAR_LINE);
}


static void stream_started_callback(pa_stream *s, void *userdata) {
  assert(s);


  if (verbose)
    fprintf(stderr, "Stream started.%s ", CLEAR_LINE);
}


static void stream_moved_callback(pa_stream *s, void *userdata) {
  assert(s); .


  if (verbose)
    fprintf(stderr, "Stream moved to device %s (%u, %ssuspended).%s ", pa_stream_get_device_name(s), pa_stream_get_device_index(s), pa_stream_is_suspended(s) ? "" : "not ",  CLEAR_LINE);
}


static void stream_buffer_attr_callback(pa_stream *s, void *userdata) {
  assert(s);


  if (verbose)
    fprintf(stderr, "Stream buffer attributes changed.%s ",  CLEAR_LINE);
}


static void stream_event_callback(pa_stream *s, const char *name, pa_proplist *pl, void *userdata) {
  char *t;


  assert(s);
  assert(name);
  assert(pl);


  t = pa_proplist_to_string_sep(pl, ", ");
  fprintf(stderr, "Got event '%s', properties '%s' ", name, t);
  pa_xfree(t);
}


/* This is called whenever new data may be written to the stream */
static void stream_write_callback(pa_stream *s, size_t length, void *userdata) {
  //assert(s); .
  //assert(length > 0);


  printf("Stream write callback: Ready to write %d bytes ", length);

  if (raw) {
    // assert(!sndfile);


    if (stdio_event)
      mainloop_api->io_enable(stdio_event, PA_IO_EVENT_INPUT);


    if (!buffer)
      return;
    printf("  do stream write from stream write callback ");
    do_stream_write(length);


  }
}


// This callback gets called when our context changes state.  We really only
// care about when it's ready or if it has failed
void state_cb(pa_context *c, void *userdata) {
  pa_context_state_t state;
  int *pa_ready = userdata;


  printf("State changed ");
  state = pa_context_get_state(c);
  switch  (state) {
    // There are just here for reference
  case PA_CONTEXT_UNCONNECTED:
  case PA_CONTEXT_CONNECTING:
  case PA_CONTEXT_AUTHORIZING:
  case PA_CONTEXT_SETTING_NAME:
  default:
    break;
  case PA_CONTEXT_FAILED:
  case PA_CONTEXT_TERMINATED:
    *pa_ready = 2;
    break;
  case PA_CONTEXT_READY: {
    pa_buffer_attr buffer_attr;


    if (verbose)
      printf("Connection established.%s ", CLEAR_LINE);


    if (!(stream = pa_stream_new(c, "JanPlayback", &sample_spec, NULL))) {
      printf("pa_stream_new() failed: %s", pa_strerror(pa_context_errno(c)));
      exit(1); // goto fail; .
    }


    pa_stream_set_state_callback(stream, stream_state_callback, NULL);

    pa_stream_set_write_callback(stream, stream_write_callback, NULL);

    pa_stream_set_read_callback(stream, stream_read_callback, NULL);

    pa_stream_set_suspended_callback(stream, stream_suspended_callback, NULL);
    pa_stream_set_moved_callback(stream, stream_moved_callback, NULL);
    pa_stream_set_underflow_callback(stream, stream_underflow_callback, NULL);
    pa_stream_set_overflow_callback(stream, stream_overflow_callback, NULL);


    pa_stream_set_started_callback(stream, stream_started_callback, NULL);

    pa_stream_set_event_callback(stream, stream_event_callback, NULL);
    pa_stream_set_buffer_attr_callback(stream, stream_buffer_attr_callback, NULL);


    pa_zero(buffer_attr);
    buffer_attr.maxlength = (uint32_t) -1;
    buffer_attr.prebuf = (uint32_t) -1;


    buffer_attr.fragsize = buffer_attr.tlength = (uint32_t) -1;
    buffer_attr.minreq = (uint32_t) -1;


    pa_cvolume cv;

    if (pa_stream_connect_playback(stream, NULL, &buffer_attr, flags,
                                   NULL,
                                   NULL) < 0) {
      printf("pa_stream_connect_playback() failed: %s", pa_strerror(pa_context_errno(c))); .
      exit(1); //goto fail;
    } else {
      printf("Set playback callback ");
    }


    pa_stream_trigger(stream, stream_success, NULL);
  }


    break;
  }
}


int main(int argc, char *argv[]) {

  struct stat st;
  off_t size;
  ssize_t nread;


  // We'll need these state variables to keep track of our requests
  int state = 0;
  int pa_ready = 0;


  if (argc != 2) {
    fprintf(stderr, "Usage: %s file ", argv[0]);
    exit(1);
  }
  // slurp the whole file into buffer
  if ((fdin = open(argv[1],  O_RDONLY)) == -1) {
    perror("open");
    exit(1);
  }


  // Create a mainloop API and connection to the default server
  mainloop = pa_mainloop_new();
  mainloop_api = pa_mainloop_get_api(mainloop);
  context = pa_context_new(mainloop_api, "test");


  // This function connects to the pulse server
  pa_context_connect(context, NULL, 0, NULL);
  printf("Connecting ");


  // This function defines a callback so the server will tell us it's state.
  // Our callback will wait for the state to be ready.  The callback will
  // modify the variable to 1 so we know when we have a connection and it's
  // ready.
  // If there's an error, the callback will set pa_ready to 2
  pa_context_set_state_callback(context, state_cb, &pa_ready); .


  if (pa_mainloop_run(mainloop, &ret) < 0) {
    printf("pa_mainloop_run() failed.");
    exit(1); // goto quit
  }
}

Controlling Latency

Managing latency is described at www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/LactencyControl .

In your code you then have to do the following when calling pa_stream_connect_playback() resp. pa_stream_connect_record():

  • Pass PA_STREAM_ADJUST_LATENCY in the flags parameter. Only if this flag is set will PA reconfigure the low-level device’s buffer size and adjust it to the latency you specify.

  • Pass a pa_buffer_attr struct in the buffer_attr parameter. In the fields of this struct, make sure to initialize every single field to (uint32_t) -1, with the exception of tlength (for playback) resp. fragsize (for recording). Initialize those to the latency you want to achieve. Use pa_usec_to_bytes(&ss, ...) to convert the latency from a time unit to bytes.

The extra code is as follows:

    // Set properties of the record buffer
    pa_zero(buffer_attr);
    buffer_attr.maxlength = (uint32_t) -1;
    buffer_attr.prebuf = (uint32_t) -1;


    if (latency_msec > 0) {
      buffer_attr.fragsize = buffer_attr.tlength = pa_usec_to_bytes(latency_msec * PA_USEC_PER_MSEC, &sample_spec);
      flags |= PA_STREAM_ADJUST_LATENCY;
    } else if (latency > 0) {
      buffer_attr.fragsize = buffer_attr.tlength = (uint32_t) latency;
      flags |= PA_STREAM_ADJUST_LATENCY;
    } else
      buffer_attr.fragsize = buffer_attr.tlength = (uint32_t) -1;


    if (process_time_msec > 0) {
      buffer_attr.minreq = pa_usec_to_bytes(process_time_msec * PA_USEC_PER_MSEC, &sample_spec);
    } else if (process_time > 0)
      buffer_attr.minreq = (uint32_t) process_time;
    else
      buffer_attr.minreq = (uint32_t) -1;

PulseAudio also has mechanisms to estimate the latency of the devices. It uses information from timing events. A timer event callback has to be declared, as follows:

 pa_context_rttime_new(context, pa_rtclock_now() + TIME_EVENT_USEC, time_event_callback, NULL))                                                                                                                          

The timer event callback is a “single shot” callback. It installs a stream update timer callback and sets up another timer callback.

void time_event_callback(pa_mainloop_api *m,
                                pa_time_event *e, const struct timeval *t,
                                void *userdata) {
    if (stream && pa_stream_get_state(stream) == PA_STREAM_READY) {
        pa_operation *o;
        if (!(o = pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL)))
          1; //pa_log(_("pa_stream_update_timing_info() failed: %s"), pa_strerror(pa_context_errno(context)));
        else
            pa_operation_unref(o);
    }


    pa_context_rttime_restart(context, e, pa_rtclock_now() + TIME_EVENT_USEC);

The stream update timer callback can then estimate the latency.

void stream_update_timing_callback(pa_stream *s, int success, void *userdata) {
    pa_usec_t l, usec;
    int negative = 0;


    // pa_assert(s);

    fprintf(stderr, "Update timing ");

    if (!success ||
        pa_stream_get_time(s, &usec) < 0 ||
        pa_stream_get_latency(s, &l, &negative) < 0) {
        fprintf(stderr, "Failed to get latency ");
        return;
    }


    fprintf(stderr, _("Time: %0.3f sec; Latency: %0.0f usec."),
            (float) usec / 1000000,
            (float) l * (negative?-1.0f:1.0f));
    fprintf(stderr, "         ");
}

With latency left to PulseAudio by setting fragsize and tlength to -1, I got the following:

Time: 0.850 sec; Latency: 850365 usec.
Time: 0.900 sec; Latency: 900446 usec.
Time: 0.951 sec; Latency: 950548 usec.
Time: 1.001 sec; Latency: 1000940 usec.
Time: 1.051 sec; Latency: 50801 usec.
Time: 1.101 sec; Latency: 100934 usec.
Time: 1.151 sec; Latency: 151007 usec.
Time: 1.201 sec; Latency: 201019 usec.
Time: 1.251 sec; Latency: 251150 usec.
Time: 1.301 sec; Latency: 301160 usec.
Time: 1.351 sec; Latency: 351218 usec.
Time: 1.401 sec; Latency: 401329 usec.
Time: 1.451 sec; Latency: 451400 usec.
Time: 1.501 sec; Latency: 501465 usec.
Time: 1.551 sec; Latency: 551587 usec.
Time: 1.602 sec; Latency: 601594 usec .

With them set to 1ms, I got the following:

Time: 1.599 sec; Latency: 939 usec.
Time: 1.649 sec; Latency: 1105 usec.
Time: 1.699 sec; Latency: -158 usec.
Time: 1.750 sec; Latency: 1020 usec.
Time: 1.800 sec; Latency: 397 usec.
Time: 1.850 sec; Latency: -52 usec.
Time: 1.900 sec; Latency: 1827 usec.
Time: 1.950 sec; Latency: 529 usec.
Time: 2.000 sec; Latency: -90 usec.
Time: 2.050 sec; Latency: 997 usec.
Time: 2.100 sec; Latency: 436 usec.
Time: 2.150 sec; Latency: 866 usec.
Time: 2.200 sec; Latency: 406 usec.
Time: 2.251 sec; Latency: 1461 usec.
Time: 2.301 sec; Latency: 107 usec.
Time: 2.351 sec; Latency: 1257 usec.

The program to do all this is parec-latency.c.

/* parec-latency.c */

#include <stdio.h>
#include <string.h>
#include <pulse/pulseaudio.h>


#define CLEAR_LINE " "
#define _(x) x


#define TIME_EVENT_USEC 50000

// From pulsecore/macro.h
#define pa_memzero(x,l) (memset((x), 0, (l)))
#define pa_zero(x) (pa_memzero(&(x), sizeof(x)))


int fdout;
char *fname = "tmp.pcm";


int verbose = 1;
int ret;


pa_context *context;

static pa_sample_spec sample_spec = {
  .format = PA_SAMPLE_S16LE,
  .rate = 44100,
  .channels = 2
};


static pa_stream *stream = NULL;

/* This is my builtin card. Use paman to find yours
   or set it to NULL to get the default device
*/
static char *device = "alsa_input.pci-0000_00_1b.0.analog-stereo";


static pa_stream_flags_t flags = 0;

static size_t latency = 0, process_time = 0;
static int32_t latency_msec = 0, process_time_msec = 0;


void stream_state_callback(pa_stream *s, void *userdata) {
  assert(s);


  switch (pa_stream_get_state(s)) {
  case PA_STREAM_CREATING:
    // The stream has been created, so
    // let's open a file to record to
    printf("Creating stream ");
    fdout = creat(fname,  0711);
    break;


  case PA_STREAM_TERMINATED:
    close(fdout);
    break;


  case PA_STREAM_READY:

    // Just for info: no functionality in this branch
    if (verbose) {
      const pa_buffer_attr *a;
      char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX];


      printf("Stream successfully created.");

      if (!(a = pa_stream_get_buffer_attr(s)))
        printf("pa_stream_get_buffer_attr() failed: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
      else {
        printf("Buffer metrics: maxlength=%u, fragsize=%u", a->maxlength, a->fragsize);


      }

      printf("Connected to device %s (%u, %ssuspended).",
             pa_stream_get_device_name(s),
             pa_stream_get_device_index(s),
             pa_stream_is_suspended(s) ? "" : "not ");
    }


    break;

  case PA_STREAM_FAILED:
  default:
    printf("Stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
    exit(1);
  }
}


/* Show the current latency */
static void stream_update_timing_callback(pa_stream *s, int success, void *userdata) {
    pa_usec_t l, usec;
    int negative = 0;


    // pa_assert(s);

    fprintf(stderr, "Update timing ");

    if (!success ||
        pa_stream_get_time(s, &usec) < 0 ||
        pa_stream_get_latency(s, &l, &negative) < 0) {
        // pa_log(_("Failed to get latency"));
        //pa_log(_("Failed to get latency: %s"), pa_strerror(pa_context_errno(context)));
        // quit(1);
        return;
    }


    fprintf(stderr, _("Time: %0.3f sec; Latency: %0.0f usec. "),
            (float) usec / 1000000,
            (float) l * (negative?-1.0f:1.0f));
    //fprintf(stderr, "         ");
}


static void time_event_callback(pa_mainloop_api *m,
                                pa_time_event *e, const struct timeval *t,
                                void *userdata) {
    if (stream && pa_stream_get_state(stream) == PA_STREAM_READY) {
        pa_operation *o;
        if (!(o = pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL)))
          1; //pa_log(_("pa_stream_update_timing_info() failed: %s"), pa_strerror(pa_context_errno(context)));
        else
            pa_operation_unref(o);
    }


    pa_context_rttime_restart(context, e, pa_rtclock_now() + TIME_EVENT_USEC);
}


void get_latency(pa_stream *s) {
  pa_usec_t latency;
  int neg;
  pa_timing_info *timing_info;


  timing_info = pa_stream_get_timing_info(s);

  if (pa_stream_get_latency(s, &latency, &neg) != 0) {
    fprintf(stderr, __FILE__": pa_stream_get_latency() failed ");
    return;
  }


  fprintf(stderr, "%0.0f usec     ", (float)latency);
}


/*********** Stream callbacks **************/

/* This is called whenever new data is available */
static void stream_read_callback(pa_stream *s, size_t length, void *userdata) {


  assert(s);
  assert(length > 0);


  // Copy the data from the server out to a file
  //fprintf(stderr, "Can read %d ", length);


  while (pa_stream_readable_size(s) > 0) {
    const void *data;
    size_t length;


    //get_latency(s);

    // peek actually creates and fills the data vbl
    if (pa_stream_peek(s, &data, &length) < 0) {
      fprintf(stderr, "Read failed ");
      exit(1);
      return;
    }
    fprintf(stderr, "Writing %d ", length);
    write(fdout, data, length);


    // swallow the data peeked at before
    pa_stream_drop(s);
  }
}


// This callback gets called when our context changes state.  We really only
// care about when it's ready or if it has failed
void state_cb(pa_context *c, void *userdata) {
  pa_context_state_t state;
  int *pa_ready = userdata;


  printf("State changed ");
  state = pa_context_get_state(c);
  switch  (state) {
    // There are just here for reference
  case PA_CONTEXT_UNCONNECTED:
  case PA_CONTEXT_CONNECTING:
  case PA_CONTEXT_AUTHORIZING:
  case PA_CONTEXT_SETTING_NAME:
  default:
    break;
  case PA_CONTEXT_FAILED:
  case PA_CONTEXT_TERMINATED:
    *pa_ready = 2;
    break;
  case PA_CONTEXT_READY: {
    pa_buffer_attr buffer_attr;


    if (verbose)
      printf("Connection established.%s ", CLEAR_LINE);


    if (!(stream = pa_stream_new(c, "JanCapture", &sample_spec, NULL))) {
      printf("pa_stream_new() failed: %s", pa_strerror(pa_context_errno(c)));
      exit(1);
    }


    // Watch for changes in the stream state to create the output file
    pa_stream_set_state_callback(stream, stream_state_callback, NULL);


    // Watch for changes in the stream's read state to write to the output file
    pa_stream_set_read_callback(stream, stream_read_callback, NULL);


    // timing info
    pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL);


    // Set properties of the record buffer
    pa_zero(buffer_attr);
    buffer_attr.maxlength = (uint32_t) -1;
    buffer_attr.prebuf = (uint32_t) -1;


    if (latency_msec > 0) {
      buffer_attr.fragsize = buffer_attr.tlength = pa_usec_to_bytes(latency_msec * PA_USEC_PER_MSEC, &sample_spec);
      flags |= PA_STREAM_ADJUST_LATENCY;
    } else if (latency > 0) {
      buffer_attr.fragsize = buffer_attr.tlength = (uint32_t) latency;
      flags |= PA_STREAM_ADJUST_LATENCY;
    } else
      buffer_attr.fragsize = buffer_attr.tlength = (uint32_t) -1;


    if (process_time_msec > 0) {
      buffer_attr.minreq = pa_usec_to_bytes(process_time_msec * PA_USEC_PER_MSEC, &sample_spec);
    } else if (process_time > 0)
      buffer_attr.minreq = (uint32_t) process_time;
    else
      buffer_attr.minreq = (uint32_t) -1;


    flags |= PA_STREAM_INTERPOLATE_TIMING;

    get_latency(stream);

    // and start recording
    if (pa_stream_connect_record(stream, device, &buffer_attr, flags) < 0) {
      printf("pa_stream_connect_record() failed: %s", pa_strerror(pa_context_errno(c)));
      exit(1);
    }
  }


    break;
  }
}


int main(int argc, char *argv[]) {

  // Define our pulse audio loop and connection variables
  pa_mainloop *pa_ml;
  pa_mainloop_api *pa_mlapi;
  pa_operation *pa_op;
  pa_time_event *time_event;


  // Create a mainloop API and connection to the default server
  pa_ml = pa_mainloop_new();
  pa_mlapi = pa_mainloop_get_api(pa_ml);
  context = pa_context_new(pa_mlapi, "test");


  // This function connects to the pulse server
  pa_context_connect(context, NULL, 0, NULL);


  // This function defines a callback so the server will tell us its state.
  pa_context_set_state_callback(context, state_cb, NULL);


  if (!(time_event = pa_context_rttime_new(context, pa_rtclock_now() + TIME_EVENT_USEC, time_event_callback, NULL))) {
    //pa_log(_("pa_context_rttime_new() failed."));
    //goto quit;
  }


  if (pa_mainloop_run(pa_ml, &ret) < 0) {
    printf("pa_mainloop_run() failed.");
    exit(1);
  }
}

Play Microphone to Speaker

Combining what you have so far, you get pa-mic-2-speaker.c.

/*
 * Copy from microphone to speaker
 * pa-mic-2-speaker.c
 */


#include <stdio.h>
#include <string.h>
#include <pulse/pulseaudio.h>


#define CLEAR_LINE " "
#define BUFF_LEN 4096


// From pulsecore/macro.h
#define pa_memzero(x,l) (memset((x), 0, (l)))
#define pa_zero(x) (pa_memzero(&(x), sizeof(x)))


static void *buffer = NULL;
static size_t buffer_length = 0, buffer_index = 0;


int verbose = 1;
int ret;


static pa_sample_spec sample_spec = {
  .format = PA_SAMPLE_S16LE,
  .rate = 44100,
  .channels = 2
};


static pa_stream *istream = NULL,
                 *ostream = NULL;


// This is my builtin card. Use paman to find yours
//static char *device = "alsa_input.pci-0000_00_1b.0.analog-stereo";
static char *idevice = NULL;
static char *odevice = NULL;


static pa_stream_flags_t flags = 0;

static size_t latency = 0, process_time = 0;
static int32_t latency_msec = 1, process_time_msec = 0;


void stream_state_callback(pa_stream *s, void *userdata) {
  assert(s);


  switch (pa_stream_get_state(s)) {
  case PA_STREAM_CREATING:
    // The stream has been created, so
    // let's open a file to record to
    printf("Creating stream ");
    // fdout = creat(fname,  0711);
    buffer = pa_xmalloc(BUFF_LEN);
    buffer_length = BUFF_LEN;
    buffer_index = 0;
    break;


  case PA_STREAM_TERMINATED:
    // close(fdout);
    break;


  case PA_STREAM_READY:

    // Just for info: no functionality in this branch
    if (verbose) {
      const pa_buffer_attr *a;
      char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX];


      printf("Stream successfully created.");

      if (!(a = pa_stream_get_buffer_attr(s)))
        printf("pa_stream_get_buffer_attr() failed: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
      else {
        printf("Buffer metrics: maxlength=%u, fragsize=%u", a->maxlength, a->fragsize);


      }

      printf("Connected to device %s (%u, %ssuspended).",
             pa_stream_get_device_name(s),
             pa_stream_get_device_index(s),
             pa_stream_is_suspended(s) ? "" : "not ");
    }


    break;

  case PA_STREAM_FAILED:
  default:
    printf("Stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
    exit(1);
  }
}


/*********** Stream callbacks **************/

/* This is called whenever new data is available */
static void stream_read_callback(pa_stream *s, size_t length, void *userdata) {


  assert(s);
  assert(length > 0);


  // Copy the data from the server out to a file
  fprintf(stderr, "Can read %d ", length);


  while (pa_stream_readable_size(s) > 0) {
    const void *data;
    size_t length, lout;


    // peek actually creates and fills the data vbl
    if (pa_stream_peek(s, &data, &length) < 0) {
      fprintf(stderr, "Read failed ");
      exit(1);
      return;
    }


    fprintf(stderr, "read %d ", length);
    lout =  pa_stream_writable_size(ostream);
    fprintf(stderr, "Writable: %d ", lout);
    if (lout == 0) {
      fprintf(stderr, "can't write, zero writable ");
      return;
    }
    if (lout < length) {
      fprintf(stderr, "Truncating read ");
      length = lout;
  }


  if (pa_stream_write(ostream, (uint8_t*) data, length, NULL, 0, PA_SEEK_RELATIVE) < 0) {
    fprintf(stderr, "pa_stream_write() failed ");
    exit(1);
    return;
  }


    // STICK OUR CODE HERE TO WRITE OUT
    //fprintf(stderr, "Writing %d ", length);
    //write(fdout, data, length);


    // swallow the data peeked at before
    pa_stream_drop(s);
  }
}


/* This is called whenever new data may be written to the stream */
// We don't actually write anything this time
static void stream_write_callback(pa_stream *s, size_t length, void *userdata) {
  //assert(s);
  //assert(length > 0);


  printf("Stream write callback: Ready to write %d bytes ", length);
 }


// This callback gets called when our context changes state.  We really only
// care about when it's ready or if it has failed
void state_cb(pa_context *c, void *userdata) {
  pa_context_state_t state;
  int *pa_ready = userdata;


  printf("State changed ");
  state = pa_context_get_state(c);
  switch  (state) {
    // There are just here for reference
  case PA_CONTEXT_UNCONNECTED:
  case PA_CONTEXT_CONNECTING:
  case PA_CONTEXT_AUTHORIZING:
  case PA_CONTEXT_SETTING_NAME:
  default:
    break;
  case PA_CONTEXT_FAILED:
  case PA_CONTEXT_TERMINATED:
    *pa_ready = 2;
    break;
  case PA_CONTEXT_READY: {
    pa_buffer_attr buffer_attr;


    if (verbose)
      printf("Connection established.%s ", CLEAR_LINE);


    if (!(istream = pa_stream_new(c, "JanCapture", &sample_spec, NULL))) {
      printf("pa_stream_new() failed: %s", pa_strerror(pa_context_errno(c)));
      exit(1);
    }


    if (!(ostream = pa_stream_new(c, "JanPlayback", &sample_spec, NULL))) {
      printf("pa_stream_new() failed: %s", pa_strerror(pa_context_errno(c)));
      exit(1);
    }


    // Watch for changes in the stream state to create the output file
    pa_stream_set_state_callback(istream, stream_state_callback, NULL);


    // Watch for changes in the stream's read state to write to the output file
    pa_stream_set_read_callback(istream, stream_read_callback, NULL);


    pa_stream_set_write_callback(ostream, stream_write_callback, NULL);

    // Set properties of the record buffer
    pa_zero(buffer_attr);
    buffer_attr.maxlength = (uint32_t) -1;
    buffer_attr.prebuf = (uint32_t) -1;


    if (latency_msec > 0) {
      buffer_attr.fragsize = buffer_attr.tlength = pa_usec_to_bytes(latency_msec * PA_USEC_PER_MSEC, &sample_spec);
      flags |= PA_STREAM_ADJUST_LATENCY;
    } else if (latency > 0) {
      buffer_attr.fragsize = buffer_attr.tlength = (uint32_t) latency;
      flags |= PA_STREAM_ADJUST_LATENCY;
    } else
      buffer_attr.fragsize = buffer_attr.tlength = (uint32_t) -1;


    if (process_time_msec > 0) {
      buffer_attr.minreq = pa_usec_to_bytes(process_time_msec * PA_USEC_PER_MSEC, &sample_spec);
    } else if (process_time > 0)
      buffer_attr.minreq = (uint32_t) process_time;
    else
      buffer_attr.minreq = (uint32_t) -1;


    // and start recording
    if (pa_stream_connect_record(istream, idevice, &buffer_attr, flags) < 0) {
      printf("pa_stream_connect_record() failed: %s", pa_strerror(pa_context_errno(c)));
      exit(1);
    }


    if (pa_stream_connect_playback(ostream, odevice, &buffer_attr, flags,
                                   NULL,
                                   NULL) < 0) {
      printf("pa_stream_connect_playback() failed: %s", pa_strerror(pa_context_errno(c)));
      exit(1); //goto fail;
    } else {
      printf("Set playback callback ");
    }


  }

    break;
  }
}


int main(int argc, char *argv[]) {

  // Define our pulse audio loop and connection variables
  pa_mainloop *pa_ml;
  pa_mainloop_api *pa_mlapi;
  pa_operation *pa_op;
  pa_context *pa_ctx;


  // Create a mainloop API and connection to the default server
  pa_ml = pa_mainloop_new();
  pa_mlapi = pa_mainloop_get_api(pa_ml);
  pa_ctx = pa_context_new(pa_mlapi, "test");


  // This function connects to the pulse server
  pa_context_connect(pa_ctx, NULL, 0, NULL);


  // This function defines a callback so the server will tell us its state.
  pa_context_set_state_callback(pa_ctx, state_cb, NULL);


  if (pa_mainloop_run(pa_ml, &ret) < 0) {
    printf("pa_mainloop_run() failed.");
    exit(1);
  }
}

When the latency is set to 1ms for everything, the actual latency is about 16ms to 28ms. I couldn’t detect it.

Setting the Volume on Devices

Each device can have its input or output volume controlled by PulseAudio. The principal called-for sinks are pa_context_set_sink_volume_by_name and pa_context_set_sink_volume_by_index, with similar calls for sources.

These calls make use of a structure called pa_cvolume. This structure can be manipulated using calls such as the following:

  • pa_cvolume_init

  • pa_cvolume_set

  • pa_cvolume_mute

In the following program, you set the volume on a particular device by reading integer values from stdin and using these to set the value. Such a loop should probably best take place in a separate thread to the PulseAudio framework. Rather than introducing application threading here, I make use of an alternative set of PulseAudio calls that set up a separate thread for the PulseAudio main loop. These calls are as follows:

  • pa_threaded_mainloop instead of pa_mainloop

  • pa_threaded_mainloop_get_api instead of pa_mainloop_get_api

  • pa_threaded_mainloop_start instead of pa_mainloop_start

The threaded calls allow you to start PulseAudio in its own thread and leave the current thread for reading volume values. This gives the relatively simple program pavolume.c.

/**
 * pavolume.c
 * Jan Newmarch
 */


#include <stdio.h>
#include <string.h>
#include <pulse/pulseaudio.h>


#define _(x) x

char *device = "alsa_output.pci-0000_00_1b.0.analog-stereo";

int ret;

pa_context *context;

void show_error(char *s) {
    fprintf(stderr, "%s ", s);
}


void volume_cb(pa_context *c, int success, void *userdata) {
    if (success)
        printf("Volume set ");
    else
        printf("Volume not set ");
}


void context_state_cb(pa_context *c, void *userdata) {

    switch (pa_context_get_state(c)) {
    case PA_CONTEXT_UNCONNECTED:
    case PA_CONTEXT_CONNECTING:
    case PA_CONTEXT_AUTHORIZING:
    case PA_CONTEXT_SETTING_NAME:
        break;


    case PA_CONTEXT_READY: {
        pa_operation *o;


        break;
    }


    case PA_CONTEXT_FAILED:
    case PA_CONTEXT_TERMINATED:
    default:
        return;
    }
}


int main(int argc, char *argv[]) {
    long volume = 0;
    char buf[128];
    struct pa_cvolume v;


    // Define our pulse audio loop and connection variables
    pa_threaded_mainloop *pa_ml;
    pa_mainloop_api *pa_mlapi;


    // Create a mainloop API and connection to the default server
    //pa_ml = pa_mainloop_new();
    pa_ml = pa_threaded_mainloop_new();
    pa_mlapi = pa_threaded_mainloop_get_api(pa_ml);
    context = pa_context_new(pa_mlapi, "Voulme control");


    // This function connects to the pulse server
    pa_context_connect(context, NULL, 0, NULL);


    // This function defines a callback so the server will tell us its state.
    pa_context_set_state_callback(context, context_state_cb, NULL);


    pa_threaded_mainloop_start(pa_ml);
    printf("Enter volume for device %s ");


    pa_cvolume_init(&v);
    while (1) {
        puts("Enter an integer 0-65536 ");
        fgets(buf, 128, stdin);
        volume = atoi(buf);
        pa_cvolume_set(&v, 2, volume);
        pa_context_set_sink_volume_by_name(context,
                                           device,
                                           &v,
                                           volume_cb,
                                           NULL
                                           );
    }
}

Listing Clients

PulseAudio is a server that talks to devices at the bottom layer and to clients at the top layer. The clients are producers and consumers of audio. One of the roles of PulseAudio is to mix signals from different source clients to shared output devices. To do this, PulseAudio keeps track of registrations by clients and can make these available to other clients with suitable callbacks.

The program palist_clients.c is similar to the program palist_devices.c. The principal difference is that when the context changes state to PA_CONTEXT_READY, the application subscribes to PA_SUBSCRIPTION_MASK_CLIENT instead of (PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE), and the subscription callback asks for pa_context_get_client_info instead of pa_context_get_source_info.

The program palist_clients.c is as follows:

/**
 * palist_clients.c
 * Jan Newmarch
 */


#include <stdio.h>
#include <string.h>
#include <pulse/pulseaudio.h>


#define CLEAR_LINE " "
#define _(x) x


// From pulsecore/macro.h
//#define pa_memzero(x,l) (memset((x), 0, (l)))
//#define pa_zero(x) (pa_memzero(&(x), sizeof(x)))


int ret;

pa_context *context;

void show_error(char *s) {
    /* stub */
}


void print_properties(pa_proplist *props) {
    void *state = NULL;


    printf("  Properties are: ");
    while (1) {
        char *key;
        if ((key = pa_proplist_iterate(props, &state)) == NULL) {
            return;
        }
        char *value = pa_proplist_gets(props, key);
        printf("   key %s, value %s ", key, value);
    }
}


void add_client_cb(pa_context *context, const pa_client_info *i, int eol, void *userdata) {

    if (eol < 0) {
        if (pa_context_errno(context) == PA_ERR_NOENTITY)
            return;


        show_error(_("Client callback failure"));
        return;
    }


    if (eol > 0) {
        return;
    }


    printf("Found a new client index %d name %s eol %d ", i->index, i->name, eol);
    print_properties(i->proplist);
}


void remove_client_cb(pa_context *context, const pa_client_info *i, int eol, void *userdata) {

    if (eol < 0) {
        if (pa_context_errno(context) == PA_ERR_NOENTITY)
            return;


        show_error(_("Client callback failure"));
        return;
    }


    if (eol > 0) {
        return;
    }


    printf("Removing a client index %d name %s ", i->index, i->name);
    print_properties(i->proplist);
}


void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) {

    switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {

    case PA_SUBSCRIPTION_EVENT_CLIENT:
        if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
            printf("Remove event at index %d ", index);
            pa_operation *o;
            if (!(o = pa_context_get_client_info(c, index, remove_client_cb, NULL))) {
                show_error(_("pa_context_get_client_info() failed"));
                return;
            }
            pa_operation_unref(o);


        } else {
            pa_operation *o;
            if (!(o = pa_context_get_client_info(c, index, add_client_cb, NULL))) {
                show_error(_("pa_context_get_client_info() failed"));
                return;
            }
            pa_operation_unref(o);
        }
        break;
    }
}


void context_state_cb(pa_context *c, void *userdata) {

    switch (pa_context_get_state(c)) {
    case PA_CONTEXT_UNCONNECTED:
    case PA_CONTEXT_CONNECTING:
    case PA_CONTEXT_AUTHORIZING:
    case PA_CONTEXT_SETTING_NAME:
        break;


    case PA_CONTEXT_READY: {
        pa_operation *o;


        pa_context_set_subscribe_callback(c, subscribe_cb, NULL);

        if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
                                       (PA_SUBSCRIPTION_MASK_CLIENT), NULL, NULL))) {
            show_error(_("pa_context_subscribe() failed"));
            return;
        }
        pa_operation_unref(o);


        if (!(o = pa_context_get_client_info_list(context,
                                                  add_client_cb,
                                                  NULL
        ) )) {
            show_error(_("pa_context_subscribe() failed"));
            return;
        }
        pa_operation_unref(o);


        break;
    }


    case PA_CONTEXT_FAILED:
        return;


    case PA_CONTEXT_TERMINATED:
    default:
        return;
    }
}


void stream_state_callback(pa_stream *s, void *userdata) {
    assert(s);


    switch (pa_stream_get_state(s)) {
    case PA_STREAM_CREATING:
        break;


    case PA_STREAM_TERMINATED:
        break;


    case PA_STREAM_READY:

        break;

    case PA_STREAM_FAILED:
    default:
        printf("Stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        exit(1);
    }
}


int main(int argc, char *argv[]) {

    // Define our pulse audio loop and connection variables
    pa_mainloop *pa_ml;
    pa_mainloop_api *pa_mlapi;
    pa_operation *pa_op;
    pa_time_event *time_event;


    // Create a mainloop API and connection to the default server
    pa_ml = pa_mainloop_new();
    pa_mlapi = pa_mainloop_get_api(pa_ml);
    context = pa_context_new(pa_mlapi, "test");


    // This function connects to the pulse server
    pa_context_connect(context, NULL, 0, NULL);


    // This function defines a callback so the server will tell us its state.
    //pa_context_set_state_callback(context, state_cb, NULL);
    pa_context_set_state_callback(context, context_state_cb, NULL);


    if (pa_mainloop_run(pa_ml, &ret) < 0) {
        printf("pa_mainloop_run() failed.");
        exit(1);
    }
}

The output on my system is as follows (elided):

Found a new client index 0 name ConsoleKit Session /org/freedesktop/ConsoleKit/Session2 eol 0
  Properties are:
   key application.name, value ConsoleKit Session /org/freedesktop/ConsoleKit/Session2
   key console-kit.session, value /org/freedesktop/ConsoleKit/Session2
Found a new client index 4 name XSMP Session on gnome-session as 1057eba7239ba1ec3d136359809598590100000018790044 eol 0
  Properties are:
   key application.name, value XSMP Session on gnome-session as 1057eba7239ba1ec3d136359809598590100000018790044
   key xsmp.vendor, value gnome-session
   key xsmp.client.id, value 1057eba7239ba1ec3d136359809598590100000018790044
Found a new client index 5 name GNOME Volume Control Media Keys eol 0
  Properties are:
   ...
Found a new client index 7 name GNOME Volume Control Applet eol 0
  Properties are:
   ...
Found a new client index 53 name Metacity eol 0
  Properties are:
  ...
Found a new client index 54 name Firefox eol 0
  Properties are:
    ...
Found a new client index 248 name PulseAudio Volume Control eol 0
  Properties are:
    ...
Found a new client index 341 name test eol 0
  Properties are:
    ...

Listing Client Sources and Sinks

Clients can act as sources; programs such as MPlayer and VLC do just that, sending streams to PulseAudio. Other clients can act as sinks. The clients themselves are monitored by the previous program. To monitor their activity, you set the mask on pa_subscribe_callback to (PA_SUBSCRIPTION_MASK_CLIENT | PA_SUBSCRIPTION_MASK_SINK_INPUT | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT). Within the subscription callback you make calls to pa_context_get_sink_input_info within the PA_SUBSCRIPTION_EVENT_SINK_INPUT branch and do the same for the source output.

The sink input callback is passed the structure pa_sink_input_info. This contains the familiar name and index fields but also has an integer field called client. This links the sink input back to the index of the client responsible for the sink. In the following program, you list all the clients as well so that these links can followed visually. Programmatically, PulseAudio makes you keep much information (such as what clients have what indices) yourself; this is ignored here.

The program to list clients and monitor changes in their input and output streams is pamonitor_clients.c.

/**
 * pamonitor_clients.c
 * Jan Newmarch
 */


#include <stdio.h>
#include <string.h>
#include <pulse/pulseaudio.h>


#define CLEAR_LINE " "
#define _(x) x


// From pulsecore/macro.h
#define pa_memzero(x,l) (memset((x), 0, (l)))
#define pa_zero(x) (pa_memzero(&(x), sizeof(x)))


int ret;

pa_context *context;

void show_error(char *s) {
    /* stub */
}


void add_client_cb(pa_context *context, const pa_client_info *i, int eol, void *userdata) {

    if (eol < 0) {
        if (pa_context_errno(context) == PA_ERR_NOENTITY)
            return;


        show_error(_("Client callback failure"));
        return;
    }


    if (eol > 0) {
        return;
    }


    printf("Found a new client index %d name %s eol %d ", i->index, i->name, eol);
}


void remove_client_cb(pa_context *context, const pa_client_info *i, int eol, void *userdata) {

    if (eol < 0) {
        if (pa_context_errno(context) == PA_ERR_NOENTITY)
            return;


        show_error(_("Client callback failure"));
        return;
    }


    if (eol > 0) {
        return;
    }


    printf("Removing a client index %d name %s ", i->index, i->name);
}


void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
    if (eol < 0) {
        if (pa_context_errno(context) == PA_ERR_NOENTITY)
            return;


        show_error(_("Sink input callback failure"));
        return;
    }


    if (eol > 0) {
        return;
    }
    printf("Sink input found index %d name %s for client %d ", i->index, i->name, i->client);
}


void source_output_cb(pa_context *c, const pa_source_output_info *i, int eol, void *userdata) {
    if (eol < 0) {
        if (pa_context_errno(context) == PA_ERR_NOENTITY)
            return;


        show_error(_("Source output callback failure"));
        return;
    }


    if (eol > 0) {
        return;
    }
    printf("Source output found index %d name %s for client %d ", i->index, i->name, i->client);
}


void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) {

    switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {

    case PA_SUBSCRIPTION_EVENT_CLIENT:
        if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
            printf("Remove event at index %d ", index);
            pa_operation *o;
            if (!(o = pa_context_get_client_info(c, index, remove_client_cb, NULL))) {
                show_error(_("pa_context_get_client_info() failed"));
                return;
            }
            pa_operation_unref(o);


        } else {
            pa_operation *o;
            if (!(o = pa_context_get_client_info(c, index, add_client_cb, NULL))) {
                show_error(_("pa_context_get_client_info() failed"));
                return;
            }
            pa_operation_unref(o);
        }
        break;


    case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
        if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
            printf("Removing sink input %d ", index);
        else {
            pa_operation *o;
            if (!(o = pa_context_get_sink_input_info(context, index, sink_input_cb, NULL))) {
                show_error(_("pa_context_get_sink_input_info() failed"));
                return;
            }
            pa_operation_unref(o);
        }
        break;


    case PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT:
        if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
            printf("Removing source output %d ", index);
        else {
            pa_operation *o;
            if (!(o = pa_context_get_source_output_info(context, index, source_output_cb, NULL))) {
                show_error(_("pa_context_get_sink_input_info() failed"));
                return;
            }
            pa_operation_unref(o);
        }
        break;
    }
}


void context_state_cb(pa_context *c, void *userdata) {

    switch (pa_context_get_state(c)) {
    case PA_CONTEXT_UNCONNECTED:
    case PA_CONTEXT_CONNECTING:
    case PA_CONTEXT_AUTHORIZING:
    case PA_CONTEXT_SETTING_NAME:
        break;


    case PA_CONTEXT_READY: {
        pa_operation *o;


        pa_context_set_subscribe_callback(c, subscribe_cb, NULL);

        if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
                                       (PA_SUBSCRIPTION_MASK_CLIENT |
                                        PA_SUBSCRIPTION_MASK_SINK_INPUT |
                                        PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT), NULL, NULL))) {
            show_error(_("pa_context_subscribe() failed"));
            return;
        }
        pa_operation_unref(o);


        if (!(o = pa_context_get_client_info_list(context,
                                                  add_client_cb,
                                                  NULL
        ) )) {
            show_error(_("pa_context_subscribe() failed"));
            return;
        }
        pa_operation_unref(o);


        break;
    }


    case PA_CONTEXT_FAILED:
        return;


    case PA_CONTEXT_TERMINATED:
    default:
        // Gtk::Main::quit();
        return;
    }
}


void stream_state_callback(pa_stream *s, void *userdata) {
    assert(s);


    switch (pa_stream_get_state(s)) {
    case PA_STREAM_CREATING:
        break;


    case PA_STREAM_TERMINATED:
        break;


    case PA_STREAM_READY:
        break;


    case PA_STREAM_FAILED:
    default:
        printf("Stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        exit(1);
    }
}


int main(int argc, char *argv[]) {

    // Define our pulse audio loop and connection variables
    pa_mainloop *pa_ml;
    pa_mainloop_api *pa_mlapi;
    pa_operation *pa_op;
    pa_time_event *time_event;


    // Create a mainloop API and connection to the default server
    pa_ml = pa_mainloop_new();
    pa_mlapi = pa_mainloop_get_api(pa_ml);
    context = pa_context_new(pa_mlapi, "test");


    // This function connects to the pulse server
    pa_context_connect(context, NULL, 0, NULL);


    // This function defines a callback so the server will tell us its state.
    //pa_context_set_state_callback(context, state_cb, NULL);
    pa_context_set_state_callback(context, context_state_cb, NULL);


    if (pa_mainloop_run(pa_ml, &ret) < 0) {
        printf("pa_mainloop_run() failed.");
        exit(1);
    }
}

The output on my system is as follows:

Found a new client index 0 name ConsoleKit Session /org/freedesktop/ConsoleKit/Session2 eol 0
Found a new client index 4 name XSMP Session on gnome-session as 1057eba7239ba1ec3d136359809598590100000018790044 eol 0
Found a new client index 5 name GNOME Volume Control Media Keys eol 0
Found a new client index 7 name GNOME Volume Control Applet eol 0
Found a new client index 53 name Metacity eol 0
Found a new client index 54 name Firefox eol 0
Found a new client index 248 name PulseAudio Volume Control eol 0
Found a new client index 342 name test eol 0

Controlling the Volume of a Sink Client

One of the significant features of PulseAudio is that not only can it mix streams to a device, but it can also control the volume of each stream. This is in addition to the volume control of each device. In pavucontrol you can see this on the Playback tab, where the volume of playback clients can be adjusted.

Programmatically this is done by calling pa_context_set_sink_input_volume with parameters that are the index of the sink input and the volume. In the following program, I follow what I did in the pavolume_client.c program where I set PulseAudio to run in a separate thread and input values for the volume in the main thread. A slight difference is that you have to wait for a client to start up a sink input, which you do by sleeping until the sink input callback assigns a nonzero value to the sink_index variable. Crude, yes. In a program such as pavucontrol, the GUI runs in separate threads anyway, and you do not need to resort to such simple tricks.

The program is pavolume_sink.c. If you play a file using, for example, MPlayer, then its volume can be adjusted by this program.

/**
 * pavolume_sink.c
 * Jan Newmarch
 */


#include <stdio.h>
#include <string.h>
#include <pulse/pulseaudio.h>


#define CLEAR_LINE " "
#define _(x) x


int ret;

// sink we will control volume on when it is non-zero
int sink_index = 0;
int sink_num_channels;


pa_context *context;

void show_error(char *s) {
    /* stub */
}


void sink_input_cb(pa_context *c, const pa_sink_input_info *i, int eol, void *userdata) {
    if (eol < 0) {
        if (pa_context_errno(context) == PA_ERR_NOENTITY)
            return;


        show_error(_("Sink input callback failure"));
        return;
    }


    if (eol > 0) {
        return;
    }
    printf("Sink input found index %d name %s for client %d ", i->index, i->name, i->client);
    sink_num_channels = i->channel_map.channels;
    sink_index = i->index;
}


void volume_cb(pa_context *c, int success, void *userdata) {
    if (success)
        printf("Volume set ");
    else
        printf("Volume not set ");
}


void subscribe_cb(pa_context *c, pa_subscription_event_type_t t, uint32_t index, void *userdata) {

    switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {

    case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
        if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
            printf("Removing sink input %d ", index);
        else {
            pa_operation *o;
            if (!(o = pa_context_get_sink_input_info(context, index, sink_input_cb, NULL))) {
                show_error(_("pa_context_get_sink_input_info() failed"));
                return;
            }
            pa_operation_unref(o);
        }
        break;
    }
}


void context_state_cb(pa_context *c, void *userdata) {

    switch (pa_context_get_state(c)) {
    case PA_CONTEXT_UNCONNECTED:
    case PA_CONTEXT_CONNECTING:
    case PA_CONTEXT_AUTHORIZING:
    case PA_CONTEXT_SETTING_NAME:
        break;


    case PA_CONTEXT_READY: {
        pa_operation *o;


        pa_context_set_subscribe_callback(c, subscribe_cb, NULL);

        if (!(o = pa_context_subscribe(c, (pa_subscription_mask_t)
                                       (PA_SUBSCRIPTION_MASK_SINK_INPUT), NULL, NULL))) {
            show_error(_("pa_context_subscribe() failed"));
            return;
        }
        break;
    }


    case PA_CONTEXT_FAILED:
        return;


    case PA_CONTEXT_TERMINATED:
    default:
        // Gtk::Main::quit();
        return;
    }
}


void stream_state_callback(pa_stream *s, void *userdata) {
    assert(s);


    switch (pa_stream_get_state(s)) {
    case PA_STREAM_CREATING:
        break;


    case PA_STREAM_TERMINATED:
        break;


    case PA_STREAM_READY:
        break;


    case PA_STREAM_FAILED:
    default:
        printf("Stream error: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        exit(1);
    }
}


int main(int argc, char *argv[]) {

    // Define our pulse audio loop and connection variables
    pa_threaded_mainloop *pa_ml;
    pa_mainloop_api *pa_mlapi;
    pa_operation *pa_op;
    pa_time_event *time_event;
    long volume = 0;
    char buf[128];
    struct pa_cvolume v;


    // Create a mainloop API and connection to the default server
    pa_ml = pa_threaded_mainloop_new();
    pa_mlapi = pa_threaded_mainloop_get_api(pa_ml);
    context = pa_context_new(pa_mlapi, "test");


    // This function connects to the pulse server
    pa_context_connect(context, NULL, 0, NULL);


    // This function defines a callback so the server will tell us its state.
    //pa_context_set_state_callback(context, state_cb, NULL);
    pa_context_set_state_callback(context, context_state_cb, NULL);


    pa_threaded_mainloop_start(pa_ml);

    /* wait till there is a sink */
    while (sink_index == 0) {
        sleep(1);
    }


    printf("Enter volume for sink %d ", sink_index);
    pa_cvolume_init(&v);
    while (1) {
        puts("Enter an integer 0-65536");
        fgets(buf, 128, stdin);
        volume = atoi(buf);
        pa_cvolume_set(&v, sink_num_channels, volume);
        pa_context_set_sink_input_volume(context,
                                           sink_index,
                                           &v,
                                           volume_cb,
                                           NULL
                                           );
    }
}

Conclusion

This chapter looked at PulseAudio. This is currently the standard sound system for consumer Linux. There are a number of utilities for exploring PulseAudio. There are two APIs: the simple API and the asynchronous API. The chapter looked at playing and recording using these APIs. Some other aspects of PulseAudio were also examined.

Latency is not a goal, and it is not designed for real-time audio. However, you can request that the latency be made small, and if PulseAudio can do it, it will give you reasonable performance. However, PulseAudio makes no guarantees about latency, so if a maximum latency is critical, then PulseAudio may not be suitable.

PulseAudio is currently built on top of ALSA and usually interacts by making itself the default ALSA plug-in.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset