參考資訊:
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