diff options
Diffstat (limited to 'gtk/soundout/pulseout.c')
-rw-r--r-- | gtk/soundout/pulseout.c | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/gtk/soundout/pulseout.c b/gtk/soundout/pulseout.c new file mode 100644 index 0000000..9dbcd6c --- /dev/null +++ b/gtk/soundout/pulseout.c @@ -0,0 +1,133 @@ +#include "pulseout.h" + +#include <stdbool.h> +#include <stdlib.h> +#include <pulse/thread-mainloop.h> +#include <pulse/stream.h> + +struct pulseout_state { + struct sound_state ss; + bool paused; + bool flush; + unsigned srate; + sound_callback cbfunc; + void *userptr; + pa_threaded_mainloop *pa_tm; + pa_context *pa_c; + pa_stream *pa_s; + bool pa_status_changed; +}; + +static void pulseout_pause(struct sound_state *ss, int pause, int flush) { + //return; + struct pulseout_state *ps = (struct pulseout_state *)ss; + if (ps->paused != !!pause) { + if (pause) { + pa_threaded_mainloop_lock(ps->pa_tm); + } + ps->paused = pause; + if (!pause) { + if (flush) ps->flush = true; + pa_threaded_mainloop_unlock(ps->pa_tm); + } + } +} + +static void pulseout_free(struct sound_state *ss) { + struct pulseout_state *ps = (struct pulseout_state *)ss; + if (ps) { + if (ps->pa_tm) { + if (ps->paused) pa_threaded_mainloop_unlock(ps->pa_tm); + pa_threaded_mainloop_stop(ps->pa_tm); + if (ps->pa_s) { + pa_stream_disconnect(ps->pa_s); + pa_stream_unref(ps->pa_s); + } + if (ps->pa_c) pa_context_unref(ps->pa_c); + pa_threaded_mainloop_free(ps->pa_tm); + } + free(ps); + } +} + +static void pulseout_cb(pa_stream *p, size_t bytes, void *userdata) { + struct pulseout_state *ps = userdata; + int16_t *buf; + pa_stream_begin_write(p, (void **)&buf, &bytes); + size_t nframes = bytes / (2 * sizeof(int16_t)); + if (!ps->paused) { + ps->cbfunc(ps->userptr, buf, nframes); + } else { + for (size_t i = 0; i < nframes; i++) { + buf[i*2+0] = 0; + buf[i*2+1] = 0; + } + } + pa_seek_mode_t smode = + ps->flush ? PA_SEEK_RELATIVE_ON_READ : PA_SEEK_RELATIVE; + pa_stream_write(p, buf, nframes * 2 * sizeof(int16_t), 0, 0, smode); + ps->flush = false; +} + +static void pa_c_cb(pa_context *pa_c, void *userdata) { + struct pulseout_state *ps = userdata; + ps->pa_status_changed = true; + pa_threaded_mainloop_signal(ps->pa_tm, 0); +} + +struct sound_state *pulseout_init( + const char *clientname, unsigned srate, + sound_callback cbfunc, void *userptr) { + struct pulseout_state *ps = malloc(sizeof(*ps)); + if (!ps) goto err; + *ps = (struct pulseout_state){ + .ss = { + .pause = pulseout_pause, + .free = pulseout_free, + .apiname = "PulseAudio", + }, + .cbfunc = cbfunc, + .userptr = userptr, + .paused = false, + .srate = srate, + }; + ps->pa_tm = pa_threaded_mainloop_new(); + if (!ps->pa_tm) goto err; + ps->pa_c = pa_context_new( + pa_threaded_mainloop_get_api(ps->pa_tm), clientname + ); + if (!ps->pa_c) goto err; + if (pa_context_connect(ps->pa_c, 0, 0, 0) < 0) goto err; + pa_context_set_state_callback(ps->pa_c, pa_c_cb, ps); + if (pa_threaded_mainloop_start(ps->pa_tm) < 0) goto err; + pa_threaded_mainloop_lock(ps->pa_tm); + ps->paused = true; + for (;;) { + while (!ps->pa_status_changed) { + pa_threaded_mainloop_wait(ps->pa_tm); + } + ps->pa_status_changed = false; + pa_context_state_t state = pa_context_get_state(ps->pa_c); + if (state == PA_CONTEXT_CONNECTING || + state == PA_CONTEXT_AUTHORIZING || + state == PA_CONTEXT_SETTING_NAME) continue; + else if (state == PA_CONTEXT_READY) break; + else goto err; + } + pa_sample_spec ss = { + .format = PA_SAMPLE_S16NE, + .rate = ps->srate, + .channels = 2, + }; + ps->pa_s = pa_stream_new( + ps->pa_c, + "stereoout", &ss, 0 + ); + if (!ps->pa_s) goto err; + pa_stream_set_write_callback(ps->pa_s, pulseout_cb, ps); + if (pa_stream_connect_playback(ps->pa_s, 0, 0, 0, 0, 0) < 0) goto err; + return &ps->ss; +err: + pulseout_free(&ps->ss); + return 0; +} |