Created
September 10, 2022 21:17
-
-
Save koonix/51ce943d8b5a86ad5144ae1522e00f8e to your computer and use it in GitHub Desktop.
example of using libpulse's async API for playback.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* source: https://stackoverflow.com/questions/29977651/how-can-the-pulseaudio-asynchronous-library-be-used-to-play-raw-pcm-data#29980624 | |
* pcm-playback: pcm-playback.c | |
* gcc -o pcm-playback pcm-playback.c `pkg-config --cflags --libs libpulse` */ | |
#include <stdio.h> | |
#include <assert.h> | |
#include <pulse/pulseaudio.h> | |
#define FORMAT PA_SAMPLE_U8 | |
#define RATE 44100 | |
void context_state_cb(pa_context* context, void* mainloop); | |
void stream_state_cb(pa_stream *s, void *mainloop); | |
void stream_success_cb(pa_stream *stream, int success, void *userdata); | |
void stream_write_cb(pa_stream *stream, size_t requested_bytes, void *userdata); | |
int main(int argc, char *argv[]) { | |
pa_threaded_mainloop *mainloop; | |
pa_mainloop_api *mainloop_api; | |
pa_context *context; | |
pa_stream *stream; | |
// Get a mainloop and its context | |
mainloop = pa_threaded_mainloop_new(); | |
assert(mainloop); | |
mainloop_api = pa_threaded_mainloop_get_api(mainloop); | |
context = pa_context_new(mainloop_api, "pcm-playback"); | |
assert(context); | |
// Set a callback so we can wait for the context to be ready | |
pa_context_set_state_callback(context, &context_state_cb, mainloop); | |
// Lock the mainloop so that it does not run and crash before the context is ready | |
pa_threaded_mainloop_lock(mainloop); | |
// Start the mainloop | |
assert(pa_threaded_mainloop_start(mainloop) == 0); | |
assert(pa_context_connect(context, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL) == 0); | |
// Wait for the context to be ready | |
for(;;) { | |
pa_context_state_t context_state = pa_context_get_state(context); | |
assert(PA_CONTEXT_IS_GOOD(context_state)); | |
if (context_state == PA_CONTEXT_READY) break; | |
pa_threaded_mainloop_wait(mainloop); | |
} | |
// Create a playback stream | |
pa_sample_spec sample_specifications; | |
sample_specifications.format = FORMAT; | |
sample_specifications.rate = RATE; | |
sample_specifications.channels = 2; | |
pa_channel_map map; | |
pa_channel_map_init_stereo(&map); | |
stream = pa_stream_new(context, "Playback", &sample_specifications, &map); | |
pa_stream_set_state_callback(stream, stream_state_cb, mainloop); | |
pa_stream_set_write_callback(stream, stream_write_cb, mainloop); | |
// recommended settings, i.e. server uses sensible values | |
pa_buffer_attr buffer_attr; | |
buffer_attr.maxlength = (uint32_t) -1; | |
buffer_attr.tlength = (uint32_t) -1; | |
buffer_attr.prebuf = (uint32_t) -1; | |
buffer_attr.minreq = (uint32_t) -1; | |
// Settings copied as per the chromium browser source | |
pa_stream_flags_t stream_flags; | |
stream_flags = PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | | |
PA_STREAM_NOT_MONOTONIC | PA_STREAM_AUTO_TIMING_UPDATE | | |
PA_STREAM_ADJUST_LATENCY; | |
// Connect stream to the default audio output sink | |
assert(pa_stream_connect_playback(stream, NULL, &buffer_attr, stream_flags, NULL, NULL) == 0); | |
// Wait for the stream to be ready | |
for(;;) { | |
pa_stream_state_t stream_state = pa_stream_get_state(stream); | |
assert(PA_STREAM_IS_GOOD(stream_state)); | |
if (stream_state == PA_STREAM_READY) break; | |
pa_threaded_mainloop_wait(mainloop); | |
} | |
pa_threaded_mainloop_unlock(mainloop); | |
// Uncork the stream so it will start playing | |
pa_stream_cork(stream, 0, stream_success_cb, mainloop); | |
// Play until we get a character | |
getc(stdin); | |
} | |
void context_state_cb(pa_context* context, void* mainloop) { | |
pa_threaded_mainloop_signal(mainloop, 0); | |
} | |
void stream_state_cb(pa_stream *s, void *mainloop) { | |
pa_threaded_mainloop_signal(mainloop, 0); | |
} | |
void stream_write_cb(pa_stream *stream, size_t requested_bytes, void *userdata) { | |
int bytes_remaining = requested_bytes; | |
while (bytes_remaining > 0) { | |
uint8_t *buffer = NULL; | |
size_t bytes_to_fill = 44100; | |
size_t i; | |
if (bytes_to_fill > bytes_remaining) bytes_to_fill = bytes_remaining; | |
pa_stream_begin_write(stream, (void**) &buffer, &bytes_to_fill); | |
for (i = 0; i < bytes_to_fill; i += 2) { | |
buffer[i] = (i%100) * 40 / 100 + 44; | |
buffer[i+1] = (i%100) * 40 / 100 + 44; | |
} | |
pa_stream_write(stream, buffer, bytes_to_fill, NULL, 0LL, PA_SEEK_RELATIVE); | |
bytes_remaining -= bytes_to_fill; | |
} | |
} | |
void stream_success_cb(pa_stream *stream, int success, void *userdata) { | |
return; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment