Set Volume via PulseAudio

I had an issue where the volume had to be controlled via API. After I decided to use PulseAudio it was a challenge to put together a minimum working example based on the official documentation.
This is an attempt to shed some light on this and is far from complete or best solution.

/**************************************************************
 *  setvol.cpp
 *  Minimum example to set the volume per PulseAudio API
 *  @version: 0.1
 *
 **************************************************************/

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

// This callback is called when the context state is changed
// We want to know if PA is ready
void pa_state_cb(pa_context *c, void *userdata) {
    pa_context_state_t state;
    int *pa_ready = (int*) userdata;

    state = pa_context_get_state(c);
    switch  (state) {
        // Only as 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_ready = 1;
            break;
    }
}

// Empty callback for setting of volume
void null_cb (pa_context* context, int success, void* userdata) {
    int *cbdata = (int *) userdata;
}

int main(int argc, char *argv[]) {
    pa_mainloop *pa_ml;
    pa_mainloop_api *pa_mlapi;
    pa_context *pa_ctx;
    pa_operation *op;

    int retval = 0;
    int pa_ready = 0;
    int* cbdata = 0;

    // command line parameter
    std::string channel;
    int volume = 0;

    if (argc <= 2) {
        printf("Program usage: setvol <channel> <volume in percent>\n");
        return -1;
    } else if(argc > 3) {
        printf("Program usage: setvol <channel> <volume in percent>\n");
        return -1;
    } else {
        channel = argv[1];
        volume = (int) round ((double) atoi(argv[2]) * (double) PA_VOLUME_NORM / 100.0);
    }

    // Create Mainloop API and connect to the Default-Server
    pa_ml = pa_mainloop_new();
    pa_mlapi = pa_mainloop_get_api(pa_ml);
    pa_ctx = pa_context_new(pa_mlapi, "PA Volume set application");
    pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);

    // Prepare the data structure for the volume
    pa_cvolume *pavol = new pa_cvolume;
    pavol->channels = 1;
    pavol->values[0] = volume;
    pavol->values[1] = 0;
    pavol->values[2] = 0;

    // This function defines a callback so that the server can share its state
    // The callback will until the server is in ready state
    // The variable pa_ready is set to 1 which means that the server is ready.
    // In case of error it returns 2
    pa_context_set_state_callback(pa_ctx, pa_state_cb, &pa_ready);

    // Wait until PA is ready
    while (pa_ready == 0) {
        pa_mainloop_iterate(pa_ml, 1, NULL);
    }

    if (pa_ready == 2) {
        retval = -1;
        goto exit;
    }

    // Set the volume
    op = pa_context_set_sink_volume_by_name (pa_ctx, channel.c_str(), pavol, null_cb, &cbdata);
    // Wait until the change is applied
    while(pa_operation_get_state(op) == PA_OPERATION_RUNNING) {
        pa_mainloop_iterate(pa_ml, 1, NULL);
    }
    pa_operation_unref(op);

exit:
    // Clean up
    pa_context_disconnect(pa_ctx);
    pa_context_unref(pa_ctx);
    pa_mainloop_free(pa_ml);

    return retval;
}

Should be working out of the box.

Thanks for reading!

Leave a Reply

Your email address will not be published. Required fields are marked *