Skip to content

PulseAudio sample (playback) ​

Adapted from the archived GNOME Wiki page Projects/Vala/PulseAudioSamples. The wiki example used GTK 2 only to run a Gtk.main loop; PulseAudio already ships a GLib mainloop adapter, so this version uses GLib.MainLoop instead.

The program connects a PulseAudio context, waits until the context is ready, configures a playback stream, and writes an interleaved stereo sine wave from the write callback.

vala
using PulseAudio;

public class AudioDevice : GLib.Object {
    PulseAudio.GLibMainLoop pa_loop;
    PulseAudio.Context context;
    Context.Flags cflags;
    PulseAudio.SampleSpec spec;
    PulseAudio.Stream.BufferAttr attr;
    double m_amp = 0.3;
    double m_freq = 500;
    double m_time = 0;
    double twopi_over_sr;

    public AudioDevice () {
        Object ();
    }

    construct {
        this.spec = PulseAudio.SampleSpec () {
            format = PulseAudio.SampleFormat.S16LE,
            channels = 2,
            rate = 44100
        };
        twopi_over_sr = (2.0 * Math.PI) / (double) spec.rate;
    }

    public void start () {
        this.pa_loop = new PulseAudio.GLibMainLoop ();
        this.context = new PulseAudio.Context (pa_loop.get_api (), null);
        this.cflags = Context.Flags.NOFAIL;
        this.context.set_state_callback (cstate_cb);

        if (this.context.connect (null, this.cflags, null) < 0) {
            print ("pa_context_connect() failed: %s\n",
                   PulseAudio.strerror (context.errno ()));
        }
    }

    public void stream_over_cb (Stream stream) {
        print ("AudioDevice: stream overflow...\n");
    }

    public void stream_under_cb (Stream stream) {
        print ("AudioDevice: stream underflow...\n");
    }

    public void write_cb (Stream stream, size_t nbytes) {
        uint len = (uint) (nbytes / sizeof (int16));
        int16[] data = new int16[len];

        for (uint i = 0; i < len; i += spec.channels) {
            int16 val = (int16) (32767.0 * m_amp * Math.sin (m_freq * m_time * twopi_over_sr));
            for (uint j = 0; j < spec.channels; j++) {
                data[i + j] = val;
            }
            this.m_time++;
        }

        stream.write ((void *) data, sizeof (int16) * data.length);
    }

    public void cstate_cb (Context context) {
        Context.State state = context.get_state ();
        if (state == Context.State.UNCONNECTED) { print ("state UNCONNECTED\n"); }
        if (state == Context.State.CONNECTING) { print ("state CONNECTING\n"); }
        if (state == Context.State.AUTHORIZING) { print ("state AUTHORIZING\n"); }
        if (state == Context.State.SETTING_NAME) { print ("state SETTING_NAME\n"); }
        if (state == Context.State.READY) { print ("state READY\n"); }
        if (state == Context.State.FAILED) { print ("state FAILED\n"); }
        if (state == Context.State.TERMINATED) { print ("state TERMINATED\n"); }

        if (state == Context.State.READY) {
            this.attr = PulseAudio.Stream.BufferAttr ();
            size_t fragment_size = 0;
            size_t n_fragments = 0;
            Stream.Flags flags = Stream.Flags.INTERPOLATE_TIMING
                | Stream.Flags.AUTO_TIMING_UPDATE
                | Stream.Flags.EARLY_REQUESTS;

            PulseAudio.Stream stream = new PulseAudio.Stream (context, "", this.spec);
            stream.set_write_callback (write_cb);
            stream.set_overflow_callback (stream_over_cb);
            stream.set_underflow_callback (stream_under_cb);

            size_t fs = spec.frame_size ();

            if ((fragment_size % fs) == 0 && n_fragments >= 2 && fragment_size > 0) {
                print ("something went wrong\n");
                return;
            }

            if (n_fragments < 2) {
                if (fragment_size > 0) {
                    n_fragments = (spec.bytes_per_second () / 2 / fragment_size);
                    if (n_fragments < 2) {
                        n_fragments = 2;
                    }
                } else {
                    n_fragments = 12;
                }
            }

            if (fragment_size <= 0) {
                fragment_size = spec.bytes_per_second () / 2 / n_fragments;
                if (fragment_size < 1024) {
                    fragment_size = 1024;
                }
            }

            print ("fragment_size: %s, n_fragments: %s, fs: %s\n",
                   fragment_size.to_string (), n_fragments.to_string (), fs.to_string ());

            attr.maxlength = (uint32) (fragment_size * (n_fragments + 1));
            attr.tlength = (uint32) (fragment_size * n_fragments);
            attr.prebuf = (uint32) fragment_size;
            attr.minreq = (uint32) fragment_size;

            int tmp = stream.connect_playback (null, attr, flags, null, null);
            if (tmp < 0) {
                print ("connect_playback returned %s\n", tmp.to_string ());
                print ("pa_stream_connect_playback() failed: %s\n",
                       PulseAudio.strerror (context.errno ()));
            }
        }
    }
}

int main (string[] args) {
    var loop = new GLib.MainLoop (null, false);
    var adev = new AudioDevice ();
    adev.start ();
    loop.run ();
    return 0;
}

Compile and run ​

shell
valac --pkg libpulse --pkg posix --pkg libpulse-mainloop-glib pulse.vala
./pulse

Stop playback with Ctrl+C.