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