程式語言 - GNU - C/C++ - PulseAudio



參考資訊:
https://github.com/notaz/pcsx_rearmed/blob/master/plugins/dfsound/pulseaudio.c#L135

main.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <pulse/pulseaudio.h>

typedef struct {
    pa_threaded_mainloop *mainloop;
    pa_context *context;
    pa_mainloop_api *api;
    pa_stream *stream;
    pa_sample_spec spec;
    int first;
} Device;

typedef struct {
    unsigned int frequency;
    unsigned int latency_in_msec;
} Settings;

static Device device = {
    .mainloop = NULL,
    .api = NULL,
    .context = NULL,
    .stream = NULL
};

static Settings settings = {
    .frequency = 44100,
    .latency_in_msec = 20,
};

const int mixlen = 3240;

const char *pa_stream_string[] = {
    "PA_STREAM_UNCONNECTED",
    "PA_STREAM_CREATING",
    "PA_STREAM_READY",
    "PA_STREAM_FAILED",
    "PA_STREAM_TERMINATED",
    NULL
};

const char *pa_context_string[] = {
    "PA_CONTEXT_UNCONNECTED",
    "PA_CONTEXT_CONNECTING",
    "PA_CONTEXT_AUTHORIZING",
    "PA_CONTEXT_SETTING_NAME",
    "PA_CONTEXT_READY",
    "PA_CONTEXT_FAILED",
    "PA_CONTEXT_TERMINATED",
    NULL
};

static void context_state_cb(pa_context *context, void *userdata)
{
    Device *dev = userdata;

    if ((context == NULL) || (dev == NULL)) {
        return;
    }

    switch (pa_context_get_state(context)) {
    case PA_CONTEXT_READY:
    case PA_CONTEXT_TERMINATED:
    case PA_CONTEXT_FAILED:
        pa_threaded_mainloop_signal(dev->mainloop, 0);
        break;
    case PA_CONTEXT_UNCONNECTED:
    case PA_CONTEXT_CONNECTING:
    case PA_CONTEXT_AUTHORIZING:
    case PA_CONTEXT_SETTING_NAME:
        break;
    }
}

static void stream_state_cb(pa_stream *stream, void * userdata)
{
    Device *dev = userdata;
    
    if ((stream == NULL) || (dev == NULL)) {
        return;
    }

    switch(pa_stream_get_state(stream)) {
    case PA_STREAM_READY:
    case PA_STREAM_FAILED:
    case PA_STREAM_TERMINATED:
        pa_threaded_mainloop_signal(dev->mainloop, 0);
        break;
    case PA_STREAM_UNCONNECTED:
    case PA_STREAM_CREATING:
        break;
    }
}

static void stream_latency_update_cb(pa_stream *stream, void *userdata)
{
    Device *dev = userdata;

    if ((stream == NULL) || (dev == NULL)) {
        return;
    }
    pa_threaded_mainloop_signal(dev->mainloop, 0);
}

static void stream_request_cb(pa_stream *stream, size_t length, void *userdata)
{
    Device *dev = userdata;

    if ((stream == NULL) || (dev == NULL)) {
        return;
    }
    pa_threaded_mainloop_signal(dev->mainloop, 0);
}

static int pulse_init(void)
{
    int r = 0;

    device.mainloop = pa_threaded_mainloop_new();
    if (device.mainloop == NULL) {
        printf("Failed to acquire PulseAudio main loop\n");
        return -1;
    }

    device.api = pa_threaded_mainloop_get_api(device.mainloop);
    device.context = pa_context_new(device.api, "main");
    pa_context_set_state_callback(device.context, context_state_cb, &device);
    if (device.context == NULL) {
        printf("Failed to acquire PulseAudio device context\n");
        return -1;
    }

    if (pa_context_connect(device.context, NULL, 0, NULL) < 0) {
        r = pa_context_errno(device.context);
        printf("Failed to connect to PulseAudio server: %s\n", pa_strerror(r));
        return -1;
    }

    pa_threaded_mainloop_lock(device.mainloop);
    if (pa_threaded_mainloop_start(device.mainloop) < 0) {
        printf("Failed to start mainloop\n");
        return -1;
    }

    pa_context_state_t context_state = { 0 };
    context_state = pa_context_get_state(device.context);
    while (context_state != PA_CONTEXT_READY) {
        context_state = pa_context_get_state(device.context);
        if (!PA_CONTEXT_IS_GOOD(context_state)) {
            r = pa_context_errno(device.context);
            printf("Context state is not good: %s\n", pa_strerror(r));
            return -1;
        }
        else if (context_state == PA_CONTEXT_READY) {
            break;
        }
        else {
            printf("PulseAudio context state is %s\n", pa_context_string[context_state]);
        }
        pa_threaded_mainloop_wait(device.mainloop);
    }

    device.spec.format = PA_SAMPLE_S16NE;
    device.spec.channels = 2;
    device.spec.rate = settings.frequency;

    pa_buffer_attr buffer_attributes = { 0 };
    buffer_attributes.tlength = pa_bytes_per_second(&device.spec) / 5;
    buffer_attributes.maxlength = buffer_attributes.tlength * 3;
    buffer_attributes.minreq = buffer_attributes.tlength / 3;
    buffer_attributes.prebuf = buffer_attributes.tlength;

    device.stream = pa_stream_new(device.context, "main", &device.spec, NULL);
    if (device.stream == NULL) {
        r = pa_context_errno(device.context);
        printf("Failed to acquire new PulseAudio stream: %s\n", pa_strerror(r));
        return -1;
    }

    pa_stream_set_state_callback(device.stream, stream_state_cb, &device);
    pa_stream_set_write_callback(device.stream, stream_request_cb, &device);
    pa_stream_set_latency_update_callback(device.stream, stream_latency_update_cb, &device);

    pa_stream_flags_t flags = (pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE);
    //pa_stream_flags_t flags = (pa_stream_flags_t)(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS);
    if (pa_stream_connect_playback(device.stream, NULL, &buffer_attributes, flags, NULL, NULL) < 0) {
        r = pa_context_errno(device.context);
        printf("Could not connect for playback: %s\n", pa_strerror(r));
        return -1;
    }

    pa_stream_state_t stream_state = { 0 };
    stream_state = pa_stream_get_state(device.stream);
    while (stream_state != PA_STREAM_READY) {
        stream_state = pa_stream_get_state(device.stream);

        if (stream_state == PA_STREAM_READY) {
            break;
        }
        else if (!PA_STREAM_IS_GOOD(stream_state)) {
            r = pa_context_errno(device.context);
            printf("Stream state is not good: %s\n", pa_strerror(r));
            return -1;
        }
        else {
            printf("PulseAudio stream state is %s\n", pa_stream_string[stream_state]);
        }
        pa_threaded_mainloop_wait(device.mainloop);
    }
    pa_threaded_mainloop_unlock(device.mainloop);
    printf("Initialize PulseAudio successfully\n");
    return 0;
}

static void pulse_finish(void)
{
    if (device.mainloop != NULL) {
        pa_threaded_mainloop_stop(device.mainloop);
    }

    if (device.stream != NULL) {
        pa_stream_unref(device.stream);
        device.stream = NULL;
    }

    if (device.context != NULL) {
        pa_context_disconnect(device.context);
        pa_context_unref(device.context);
        device.context = NULL;
    }

    if (device.mainloop != NULL) {
        pa_threaded_mainloop_free(device.mainloop);
        device.mainloop = NULL;
    }
}

static int pulse_busy(void)
{
    int free_space = 0;

    if ((device.mainloop == NULL) || (device.api == NULL) || ( device.context == NULL) || (device.stream == NULL)) {
        return 1;
    }

    pa_threaded_mainloop_lock(device.mainloop);
    free_space = pa_stream_writable_size(device.stream);
    pa_threaded_mainloop_unlock(device.mainloop);

    if (free_space < (mixlen * 3)) {
        return 1;
    }
    return 0;
}

static void pulse_feed(void *pSound, int lBytes)
{
    if (device.mainloop != NULL) {
        pa_threaded_mainloop_lock(device.mainloop);
        if (pa_stream_write(device.stream, pSound, lBytes, NULL, 0LL, PA_SEEK_RELATIVE) < 0) {
            printf("Failed to perform write\n");
        }
        else {
            pa_threaded_mainloop_unlock(device.mainloop);
        }
    }
}

int main(int argc, char *argv[])
{
    int i = 0;
    float frequency = 440.0;
    short buffer[22050 * 4] = { 0 };

    for (i = 0; i < 22050; i++) {
        buffer[i * 2] = (short)(32767.0 * sin((2.0 * M_PI * frequency * i) / 44100.0));
        buffer[i * 2 + 1] = buffer[i * 2];
    }

    pulse_init();
    if (!pulse_busy()) {
        pulse_feed(buffer, sizeof(buffer));
    }
    usleep(1000000);
    pulse_finish();
    return 0;
}

編譯、執行

$ gcc main.c -o main -lpulse -lm
$ ./main