123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801 |
- diff --git a/audio.c b/audio.c
- --- a/audio.c
- +++ b/audio.c
- @@ -32,6 +32,9 @@
- #include "log.h"
- #include "lists.h"
-
- +#ifdef HAVE_PULSE
- +# include "pulse.h"
- +#endif
- #ifdef HAVE_OSS
- # include "oss.h"
- #endif
- @@ -893,6 +896,15 @@
- }
- #endif
-
- +#ifdef HAVE_PULSE
- + if (!strcasecmp(name, "pulseaudio")) {
- + pulse_funcs (funcs);
- + printf ("Trying PulseAudio...\n");
- + if (funcs->init(&hw_caps))
- + return;
- + }
- +#endif
- +
- #ifdef HAVE_OSS
- if (!strcasecmp(name, "oss")) {
- oss_funcs (funcs);
- diff --git a/configure.in b/configure.in
- --- a/configure.in
- +++ b/configure.in
- @@ -162,6 +162,21 @@
- AC_MSG_ERROR([BerkeleyDB (libdb) not found.]))
- fi
-
- +AC_ARG_WITH(pulse, AS_HELP_STRING(--without-pulse,
- + Compile without PulseAudio support.))
- +
- +if test "x$with_pulse" != "xno"
- +then
- + PKG_CHECK_MODULES(PULSE, [libpulse],
- + [SOUND_DRIVERS="$SOUND_DRIVERS PULSE"
- + EXTRA_OBJS="$EXTRA_OBJS pulse.o"
- + AC_DEFINE([HAVE_PULSE], 1, [Define if you have PulseAudio.])
- + EXTRA_LIBS="$EXTRA_LIBS $PULSE_LIBS"
- + CFLAGS="$CFLAGS $PULSE_CFLAGS"],
- + [true])
- +fi
- +
- +
- AC_ARG_WITH(oss, AS_HELP_STRING([--without-oss],
- [Compile without OSS support]))
-
- diff --git a/options.c b/options.c
- --- a/options.c
- +++ b/options.c
- @@ -572,10 +572,11 @@
-
- #ifdef OPENBSD
- add_list ("SoundDriver", "SNDIO:JACK:OSS",
- - CHECK_DISCRETE(5), "SNDIO", "Jack", "ALSA", "OSS", "null");
- + CHECK_DISCRETE(5), "SNDIO", "PulseAudio", "Jack", "ALSA", "OSS", "null");
- +
- #else
- add_list ("SoundDriver", "Jack:ALSA:OSS",
- - CHECK_DISCRETE(5), "SNDIO", "Jack", "ALSA", "OSS", "null");
- + CHECK_DISCRETE(5), "SNDIO", "PulseAudio", "Jack", "ALSA", "OSS", "null");
- #endif
-
- add_str ("JackClientName", "moc", CHECK_NONE);
- diff --git a/pulse.c b/pulse.c
- new file mode 100644
- --- /dev/null
- +++ b/pulse.c
- @@ -0,0 +1,705 @@
- +/*
- + * MOC - music on console
- + * Copyright (C) 2011 Marien Zwart <marienz@marienz.net>
- + *
- + * This program is free software; you can redistribute it and/or modify
- + * it under the terms of the GNU General Public License as published by
- + * the Free Software Foundation; either version 2 of the License, or
- + * (at your option) any later version.
- + *
- + */
- +
- +/* PulseAudio backend.
- + *
- + * FEATURES:
- + *
- + * Does not autostart a PulseAudio server, but uses an already-started
- + * one, which should be better than alsa-through-pulse.
- + *
- + * Supports control of either our stream's or our entire sink's volume
- + * while we are actually playing. Volume control while paused is
- + * intentionally unsupported: the PulseAudio documentation strongly
- + * suggests not passing in an initial volume when creating a stream
- + * (allowing the server to track this instead), and we do not know
- + * which sink to control if we do not have a stream open.
- + *
- + * IMPLEMENTATION:
- + *
- + * Most client-side (resource allocation) errors are fatal. Failure to
- + * create a server context or stream is not fatal (and MOC should cope
- + * with these failures too), but server communication failures later
- + * on are currently not handled (MOC has no great way for us to tell
- + * it we no longer work, and I am not sure if attempting to reconnect
- + * is worth it or even a good idea).
- + *
- + * The pulse "simple" API is too simple: it combines connecting to the
- + * server and opening a stream into one operation, while I want to
- + * connect to the server when MOC starts (and fall back to a different
- + * backend if there is no server), and I cannot open a stream at that
- + * time since I do not know the audio format yet.
- + *
- + * PulseAudio strongly recommends we use a high-latency connection,
- + * which the MOC frontend code might not expect from its audio
- + * backend. We'll see.
- + *
- + * We map MOC's percentage volumes linearly to pulse's PA_VOLUME_MUTED
- + * (0) .. PA_VOLUME_NORM range. This is what the PulseAudio docs recommend
- + * ( http://pulseaudio.org/wiki/WritingVolumeControlUIs ). It does mean
- + * PulseAudio volumes above PA_VOLUME_NORM do not work well with MOC.
- + *
- + * Comments in audio.h claim "All functions are executed only by one
- + * thread" (referring to the function in the hw_funcs struct). This is
- + * a blatant lie. Most of them are invoked off the "output buffer"
- + * thread (out_buf.c) but at least the "playing" thread (audio.c)
- + * calls audio_close which calls our close function. We can mostly
- + * ignore this problem because we serialize on the pulseaudio threaded
- + * mainloop lock. But it does mean that functions that are normally
- + * only called between open and close (like reset) are sometimes
- + * called without us having a stream. Bulletproof, therefore:
- + * serialize setting/unsetting our global stream using the threaded
- + * mainloop lock, and check for that stream being non-null before
- + * using it.
- + *
- + * I am not convinced there are no further dragons lurking here: can
- + * the "playing" thread(s) close and reopen our output stream while
- + * the "output buffer" thread is sending output there? We can bail if
- + * our stream is simply closed, but we do not currently detect it
- + * being reopened and no longer using the same sample format, which
- + * might have interesting results...
- + *
- + * Also, read_mixer is called from the main server thread (handling
- + * commands). This crashed me once when it got at a stream that was in
- + * the "creating" state and therefore did not have a valid stream
- + * index yet. Fixed by only assigning to the stream global when the
- + * stream is valid.
- + */
- +
- +#ifdef HAVE_CONFIG_H
- +# include "config.h"
- +#endif
- +
- +#define DEBUG
- +
- +#include <pulse/pulseaudio.h>
- +#include "common.h"
- +#include "log.h"
- +#include "audio.h"
- +
- +
- +/* The pulse mainloop and context are initialized in pulse_init and
- + * destroyed in pulse_shutdown.
- + */
- +static pa_threaded_mainloop *mainloop = NULL;
- +static pa_context *context = NULL;
- +
- +/* The stream is initialized in pulse_open and destroyed in pulse_close. */
- +static pa_stream *stream = NULL;
- +
- +static int showing_sink_volume = 0;
- +
- +/* Callbacks that do nothing but wake up the mainloop. */
- +
- +static void context_state_callback (pa_context *context ATTR_UNUSED,
- + void *userdata)
- +{
- + pa_threaded_mainloop *m = userdata;
- +
- + pa_threaded_mainloop_signal (m, 0);
- +}
- +
- +static void stream_state_callback (pa_stream *stream ATTR_UNUSED,
- + void *userdata)
- +{
- + pa_threaded_mainloop *m = userdata;
- +
- + pa_threaded_mainloop_signal (m, 0);
- +}
- +
- +static void stream_write_callback (pa_stream *stream ATTR_UNUSED,
- + size_t nbytes ATTR_UNUSED, void *userdata)
- +{
- + pa_threaded_mainloop *m = userdata;
- +
- + pa_threaded_mainloop_signal (m, 0);
- +}
- +
- +/* Initialize pulse mainloop and context. Failure to connect to the
- + * pulse daemon is nonfatal, everything else is fatal (as it
- + * presumably means we ran out of resources).
- + */
- +static int pulse_init (struct output_driver_caps *caps)
- +{
- + pa_context *c;
- + pa_proplist *proplist;
- +
- + assert (!mainloop);
- + assert (!context);
- +
- + mainloop = pa_threaded_mainloop_new ();
- + if (!mainloop)
- + fatal ("Cannot create PulseAudio mainloop");
- +
- + if (pa_threaded_mainloop_start (mainloop) < 0)
- + fatal ("Cannot start PulseAudio mainloop");
- +
- + /* TODO: possibly add more props.
- + *
- + * There are a few we could set in proplist.h but nothing I
- + * expect to be very useful.
- + *
- + * http://pulseaudio.org/wiki/ApplicationProperties recommends
- + * setting at least application.name, icon.name and media.role.
- + *
- + * No need to set application.name here, the name passed to
- + * pa_context_new_with_proplist overrides it.
- + */
- + proplist = pa_proplist_new ();
- + if (!proplist)
- + fatal ("Cannot allocate PulseAudio proplist");
- +
- + pa_proplist_sets (proplist,
- + PA_PROP_APPLICATION_VERSION, PACKAGE_VERSION);
- + pa_proplist_sets (proplist, PA_PROP_MEDIA_ROLE, "music");
- + pa_proplist_sets (proplist, PA_PROP_APPLICATION_ID, "net.daper.moc");
- +
- + pa_threaded_mainloop_lock (mainloop);
- +
- + c = pa_context_new_with_proplist (
- + pa_threaded_mainloop_get_api (mainloop),
- + PACKAGE_NAME, proplist);
- + pa_proplist_free (proplist);
- +
- + if (!c)
- + fatal ("Cannot allocate PulseAudio context");
- +
- + pa_context_set_state_callback (c, context_state_callback, mainloop);
- +
- + /* Ignore return value, rely on state being set properly */
- + pa_context_connect (c, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
- +
- + while (1) {
- + pa_context_state_t state = pa_context_get_state (c);
- +
- + if (state == PA_CONTEXT_READY)
- + break;
- +
- + if (!PA_CONTEXT_IS_GOOD (state)) {
- + error ("PulseAudio connection failed: %s",
- + pa_strerror (pa_context_errno (c)));
- +
- + goto unlock_and_fail;
- + }
- +
- + debug ("waiting for context to become ready...");
- + pa_threaded_mainloop_wait (mainloop);
- + }
- +
- + /* Only set the global now that the context is actually ready */
- + context = c;
- +
- + pa_threaded_mainloop_unlock (mainloop);
- +
- + /* We just make up the hardware capabilities, since pulse is
- + * supposed to be abstracting these out. Assume pulse will
- + * deal with anything we want to throw at it, and that we will
- + * only want mono or stereo audio.
- + */
- + caps->min_channels = 1;
- + caps->max_channels = 2;
- + caps->formats = (SFMT_S8 | SFMT_S16 | SFMT_S32 |
- + SFMT_FLOAT | SFMT_BE | SFMT_LE);
- +
- + return 1;
- +
- +unlock_and_fail:
- +
- + pa_context_unref (c);
- +
- + pa_threaded_mainloop_unlock (mainloop);
- +
- + pa_threaded_mainloop_stop (mainloop);
- + pa_threaded_mainloop_free (mainloop);
- + mainloop = NULL;
- +
- + return 0;
- +}
- +
- +static void pulse_shutdown (void)
- +{
- + pa_threaded_mainloop_lock (mainloop);
- +
- + pa_context_disconnect (context);
- + pa_context_unref (context);
- + context = NULL;
- +
- + pa_threaded_mainloop_unlock (mainloop);
- +
- + pa_threaded_mainloop_stop (mainloop);
- + pa_threaded_mainloop_free (mainloop);
- + mainloop = NULL;
- +}
- +
- +static int pulse_open (struct sound_params *sound_params)
- +{
- + pa_sample_spec ss;
- + pa_buffer_attr ba;
- + pa_stream *s;
- +
- + assert (!stream);
- + /* Initialize everything to -1, which in practice gets us
- + * about 2 seconds of latency (which is fine). This is not the
- + * same as passing NULL for this struct, which gets us an
- + * unnecessarily short alsa-like latency.
- + */
- + ba.fragsize = (uint32_t) -1;
- + ba.tlength = (uint32_t) -1;
- + ba.prebuf = (uint32_t) -1;
- + ba.minreq = (uint32_t) -1;
- + ba.maxlength = (uint32_t) -1;
- +
- + ss.channels = sound_params->channels;
- + ss.rate = sound_params->rate;
- + switch (sound_params->fmt) {
- + case SFMT_U8:
- + ss.format = PA_SAMPLE_U8;
- + break;
- + case SFMT_S16 | SFMT_LE:
- + ss.format = PA_SAMPLE_S16LE;
- + break;
- + case SFMT_S16 | SFMT_BE:
- + ss.format = PA_SAMPLE_S16BE;
- + break;
- + case SFMT_FLOAT | SFMT_LE:
- + ss.format = PA_SAMPLE_FLOAT32LE;
- + break;
- + case SFMT_FLOAT | SFMT_BE:
- + ss.format = PA_SAMPLE_FLOAT32BE;
- + break;
- + case SFMT_S32 | SFMT_LE:
- + ss.format = PA_SAMPLE_S32LE;
- + break;
- + case SFMT_S32 | SFMT_BE:
- + ss.format = PA_SAMPLE_S32BE;
- + break;
- +
- + default:
- + fatal ("pulse: got unrequested format");
- + }
- +
- + debug ("opening stream");
- +
- + pa_threaded_mainloop_lock (mainloop);
- +
- + /* TODO: figure out if there are useful stream properties to set.
- + *
- + * I do not really see any in proplist.h that we can set from
- + * here (there are media title/artist/etc props but we do not
- + * have that data available here).
- + */
- + s = pa_stream_new (context, "music", &ss, NULL);
- + if (!s)
- + fatal ("pulse: stream allocation failed");
- +
- + pa_stream_set_state_callback (s, stream_state_callback, mainloop);
- + pa_stream_set_write_callback (s, stream_write_callback, mainloop);
- +
- + /* Ignore return value, rely on failed stream state instead. */
- + pa_stream_connect_playback (
- + s, NULL, &ba,
- + PA_STREAM_INTERPOLATE_TIMING |
- + PA_STREAM_AUTO_TIMING_UPDATE |
- + PA_STREAM_ADJUST_LATENCY,
- + NULL, NULL);
- +
- + while (1) {
- + pa_stream_state_t state = pa_stream_get_state (s);
- +
- + if (state == PA_STREAM_READY)
- + break;
- +
- + if (!PA_STREAM_IS_GOOD (state)) {
- + error ("PulseAudio stream connection failed");
- +
- + goto fail;
- + }
- +
- + debug ("waiting for stream to become ready...");
- + pa_threaded_mainloop_wait (mainloop);
- + }
- +
- + /* Only set the global stream now that it is actually ready */
- + stream = s;
- +
- + pa_threaded_mainloop_unlock (mainloop);
- +
- + return 1;
- +
- +fail:
- + pa_stream_unref (s);
- +
- + pa_threaded_mainloop_unlock (mainloop);
- + return 0;
- +}
- +
- +static void pulse_close (void)
- +{
- + debug ("closing stream");
- +
- + pa_threaded_mainloop_lock (mainloop);
- +
- + pa_stream_disconnect (stream);
- + pa_stream_unref (stream);
- + stream = NULL;
- +
- + pa_threaded_mainloop_unlock (mainloop);
- +}
- +
- +static int pulse_play (const char *buff, const size_t size)
- +{
- + size_t offset = 0;
- +
- + debug ("Got %d bytes to play", (int)size);
- +
- + pa_threaded_mainloop_lock (mainloop);
- +
- + /* The buffer is usually writable when we get here, and there
- + * are usually few (if any) writes after the first one. So
- + * there is no point in doing further writes directly from the
- + * callback: we can just do all writes from this thread.
- + */
- +
- + /* Break out of the loop if some other thread manages to close
- + * our stream underneath us.
- + */
- + while (stream) {
- + size_t towrite = MIN(pa_stream_writable_size (stream),
- + size - offset);
- + debug ("writing %d bytes", (int)towrite);
- +
- + /* We have no working way of dealing with errors
- + * (see below). */
- + if (pa_stream_write(stream, buff + offset, towrite,
- + NULL, 0, PA_SEEK_RELATIVE))
- + error ("pa_stream_write failed");
- +
- + offset += towrite;
- +
- + if (offset >= size)
- + break;
- +
- + pa_threaded_mainloop_wait (mainloop);
- + }
- +
- + pa_threaded_mainloop_unlock (mainloop);
- +
- + debug ("Done playing!");
- +
- + /* We should always return size, calling code does not deal
- + * well with anything else. Only read the rest if you want to
- + * know why.
- + *
- + * The output buffer reader thread (out_buf.c:read_thread)
- + * repeatedly loads some 64k/0.1s of audio into a buffer on
- + * the stack, then calls audio_send_pcm repeatedly until this
- + * entire buffer has been processed (similar to the loop in
- + * this function). audio_send_pcm applies the softmixer and
- + * equalizer, then feeds the result to this function, passing
- + * through our return value.
- + *
- + * So if we return less than size the equalizer/softmixer is
- + * re-applied to the remaining data, which is silly. Also,
- + * audio_send_pcm checks for our return value being zero and
- + * calls fatal() if it is, so try to always process *some*
- + * data. Also, out_buf.c uses the return value of this
- + * function from the last run through its inner loop to update
- + * its time attribute, which means it will be interestingly
- + * off if that loop ran more than once.
- + *
- + * Oh, and alsa.c seems to think it can return -1 to indicate
- + * failure, which will cause out_buf.c to rewind its buffer
- + * (to before its start, usually).
- + */
- + return size;
- +}
- +
- +static void volume_cb (const pa_cvolume *v, void *userdata)
- +{
- + int *result = userdata;
- +
- + if (v)
- + *result = 100 * pa_cvolume_avg (v) / PA_VOLUME_NORM;
- +
- + pa_threaded_mainloop_signal (mainloop, 0);
- +}
- +
- +static void sink_volume_cb (pa_context *c ATTR_UNUSED,
- + const pa_sink_info *i, int eol ATTR_UNUSED,
- + void *userdata)
- +{
- + volume_cb (i ? &i->volume : NULL, userdata);
- +}
- +
- +static void sink_input_volume_cb (pa_context *c ATTR_UNUSED,
- + const pa_sink_input_info *i,
- + int eol ATTR_UNUSED,
- + void *userdata ATTR_UNUSED)
- +{
- + volume_cb (i ? &i->volume : NULL, userdata);
- +}
- +
- +static int pulse_read_mixer (void)
- +{
- + pa_operation *op;
- + int result = 0;
- +
- + debug ("read mixer");
- +
- + pa_threaded_mainloop_lock (mainloop);
- +
- + if (stream) {
- + if (showing_sink_volume)
- + op = pa_context_get_sink_info_by_index (
- + context, pa_stream_get_device_index (stream),
- + sink_volume_cb, &result);
- + else
- + op = pa_context_get_sink_input_info (
- + context, pa_stream_get_index (stream),
- + sink_input_volume_cb, &result);
- +
- + while (pa_operation_get_state (op) == PA_OPERATION_RUNNING)
- + pa_threaded_mainloop_wait (mainloop);
- +
- + pa_operation_unref (op);
- + }
- +
- + pa_threaded_mainloop_unlock (mainloop);
- +
- + return result;
- +}
- +
- +static void pulse_set_mixer (int vol)
- +{
- + pa_cvolume v;
- + pa_operation *op;
- +
- + /* Setting volume for one channel does the right thing. */
- + pa_cvolume_set(&v, 1, vol * PA_VOLUME_NORM / 100);
- +
- + pa_threaded_mainloop_lock (mainloop);
- +
- + if (stream) {
- + if (showing_sink_volume)
- + op = pa_context_set_sink_volume_by_index (
- + context, pa_stream_get_device_index (stream),
- + &v, NULL, NULL);
- + else
- + op = pa_context_set_sink_input_volume (
- + context, pa_stream_get_index (stream),
- + &v, NULL, NULL);
- +
- + pa_operation_unref (op);
- + }
- +
- + pa_threaded_mainloop_unlock (mainloop);
- +}
- +
- +static int pulse_get_buff_fill (void)
- +{
- + /* This function is problematic. MOC uses it to for the "time
- + * remaining" in the UI, but calls it more than once per
- + * second (after each chunk of audio played, not for each
- + * playback time update). We have to be fairly accurate here
- + * for that time remaining to not jump weirdly. But PulseAudio
- + * cannot give us a 100% accurate value here, as it involves a
- + * server roundtrip. And if we call this a lot it suggests
- + * switching to a mode where the value is interpolated, making
- + * it presumably more inaccurate (see the flags we pass to
- + * pa_stream_connect_playback).
- + *
- + * MOC also contains what I believe to be a race: it calls
- + * audio_get_buff_fill "soon" (after playing the first chunk)
- + * after starting playback of the next song, at which point we
- + * still have part of the previous song buffered. This means
- + * our position into the new song is negative, which fails an
- + * assert (in out_buf.c:out_buf_time_get). There is no sane
- + * way for us to detect this condition. I believe no other
- + * backend triggers this because the assert sits after an
- + * implicit float -> int seconds conversion, which means we
- + * have to be off by at least an entire second to get a
- + * negative value, and none of the other backends have buffers
- + * that large (alsa buffers are supposedly a few 100 ms).
- + */
- + pa_usec_t buffered_usecs = 0;
- + int buffered_bytes = 0;
- +
- + pa_threaded_mainloop_lock (mainloop);
- +
- + /* Using pa_stream_get_timing_info and returning the distance
- + * between write_index and read_index would be more obvious,
- + * but because of how the result is actually used I believe
- + * using the latency value is slightly more correct, and it
- + * makes the following crash-avoidance hack more obvious.
- + */
- +
- + /* This function will frequently fail the first time we call
- + * it (pulse does not have the requested data yet). We ignore
- + * that and just return 0.
- + *
- + * Deal with stream being NULL too, just in case this is
- + * called in a racy fashion similar to how reset() is.
- + */
- + if (stream &&
- + pa_stream_get_latency (stream, &buffered_usecs, NULL) >= 0) {
- + /* Crash-avoidance HACK: floor our latency to at most
- + * 1 second. It is usually more, but reporting that at
- + * the start of playback crashes MOC, and we cannot
- + * sanely detect when reporting it is safe.
- + */
- + if (buffered_usecs > 1000000)
- + buffered_usecs = 1000000;
- +
- + buffered_bytes = pa_usec_to_bytes (
- + buffered_usecs,
- + pa_stream_get_sample_spec (stream));
- + }
- +
- + pa_threaded_mainloop_unlock (mainloop);
- +
- + debug ("buffer fill: %d usec / %d bytes",
- + (int) buffered_usecs, (int) buffered_bytes);
- +
- + return buffered_bytes;
- +}
- +
- +static void flush_callback (pa_stream *s ATTR_UNUSED, int success,
- + void *userdata)
- +{
- + int *result = userdata;
- +
- + *result = success;
- +
- + pa_threaded_mainloop_signal (mainloop, 0);
- +}
- +
- +static int pulse_reset (void)
- +{
- + pa_operation *op;
- + int result = 0;
- +
- + debug ("reset requested");
- +
- + pa_threaded_mainloop_lock (mainloop);
- +
- + /* We *should* have a stream here, but MOC is racy, so bulletproof */
- + if (stream) {
- + op = pa_stream_flush (stream, flush_callback, &result);
- +
- + while (pa_operation_get_state (op) == PA_OPERATION_RUNNING)
- + pa_threaded_mainloop_wait (mainloop);
- +
- + pa_operation_unref (op);
- + } else
- + logit ("pulse_reset() called without a stream");
- +
- + pa_threaded_mainloop_unlock (mainloop);
- +
- + return result;
- +}
- +
- +static int pulse_get_rate (void)
- +{
- + /* This is called once right after open. Do not bother making
- + * this fast. */
- +
- + int result;
- +
- + pa_threaded_mainloop_lock (mainloop);
- +
- + if (stream)
- + result = pa_stream_get_sample_spec (stream)->rate;
- + else {
- + error ("get_rate called without a stream");
- + result = 0;
- + }
- +
- + pa_threaded_mainloop_unlock (mainloop);
- +
- + return result;
- +}
- +
- +static void pulse_toggle_mixer_channel (void)
- +{
- + showing_sink_volume = !showing_sink_volume;
- +}
- +
- +static void sink_name_cb (pa_context *c ATTR_UNUSED,
- + const pa_sink_info *i, int eol ATTR_UNUSED,
- + void *userdata)
- +{
- + char **result = userdata;
- +
- + if (i && !*result)
- + *result = xstrdup (i->name);
- +
- + pa_threaded_mainloop_signal (mainloop, 0);
- +}
- +
- +static void sink_input_name_cb (pa_context *c ATTR_UNUSED,
- + const pa_sink_input_info *i,
- + int eol ATTR_UNUSED,
- + void *userdata)
- +{
- + char **result = userdata;
- +
- + if (i && !*result)
- + *result = xstrdup (i->name);
- +
- + pa_threaded_mainloop_signal (mainloop, 0);
- +}
- +
- +static char *pulse_get_mixer_channel_name (void)
- +{
- + char *result = NULL;
- + pa_operation *op;
- +
- + pa_threaded_mainloop_lock (mainloop);
- +
- + if (stream) {
- + if (showing_sink_volume)
- + op = pa_context_get_sink_info_by_index (
- + context, pa_stream_get_device_index (stream),
- + sink_name_cb, &result);
- + else
- + op = pa_context_get_sink_input_info (
- + context, pa_stream_get_index (stream),
- + sink_input_name_cb, &result);
- +
- + while (pa_operation_get_state (op) == PA_OPERATION_RUNNING)
- + pa_threaded_mainloop_wait (mainloop);
- +
- + pa_operation_unref (op);
- + }
- +
- + pa_threaded_mainloop_unlock (mainloop);
- +
- + if (!result)
- + result = xstrdup ("disconnected");
- +
- + return result;
- +}
- +
- +void pulse_funcs (struct hw_funcs *funcs)
- +{
- + funcs->init = pulse_init;
- + funcs->shutdown = pulse_shutdown;
- + funcs->open = pulse_open;
- + funcs->close = pulse_close;
- + funcs->play = pulse_play;
- + funcs->read_mixer = pulse_read_mixer;
- + funcs->set_mixer = pulse_set_mixer;
- + funcs->get_buff_fill = pulse_get_buff_fill;
- + funcs->reset = pulse_reset;
- + funcs->get_rate = pulse_get_rate;
- + funcs->toggle_mixer_channel = pulse_toggle_mixer_channel;
- + funcs->get_mixer_channel_name = pulse_get_mixer_channel_name;
- +}
- diff --git a/pulse.h b/pulse.h
- new file mode 100644
- --- /dev/null
- +++ b/pulse.h
- @@ -0,0 +1,14 @@
- +#ifndef PULSE_H
- +#define PULSE_H
- +
- +#ifdef __cplusplus
- +extern "C" {
- +#endif
- +
- +void pulse_funcs (struct hw_funcs *funcs);
- +
- +#ifdef __cplusplus
- +}
- +#endif
- +
- +#endif
|