1diff --git a/audio.c b/audio.c
2--- a/audio.c
3+++ b/audio.c
4@@ -32,6 +32,9 @@
5 #include "log.h"
6 #include "lists.h"
7
8+#ifdef HAVE_PULSE
9+# include "pulse.h"
10+#endif
11 #ifdef HAVE_OSS
12 # include "oss.h"
13 #endif
14@@ -893,6 +896,15 @@
15 }
16 #endif
17
18+#ifdef HAVE_PULSE
19+ if (!strcasecmp(name, "pulseaudio")) {
20+ pulse_funcs (funcs);
21+ printf ("Trying PulseAudio...\n");
22+ if (funcs->init(&hw_caps))
23+ return;
24+ }
25+#endif
26+
27 #ifdef HAVE_OSS
28 if (!strcasecmp(name, "oss")) {
29 oss_funcs (funcs);
30diff --git a/configure.in b/configure.in
31--- a/configure.in
32+++ b/configure.in
33@@ -162,6 +162,21 @@
34 AC_MSG_ERROR([BerkeleyDB (libdb) not found.]))
35 fi
36
37+AC_ARG_WITH(pulse, AS_HELP_STRING(--without-pulse,
38+ Compile without PulseAudio support.))
39+
40+if test "x$with_pulse" != "xno"
41+then
42+ PKG_CHECK_MODULES(PULSE, [libpulse],
43+ [SOUND_DRIVERS="$SOUND_DRIVERS PULSE"
44+ EXTRA_OBJS="$EXTRA_OBJS pulse.o"
45+ AC_DEFINE([HAVE_PULSE], 1, [Define if you have PulseAudio.])
46+ EXTRA_LIBS="$EXTRA_LIBS $PULSE_LIBS"
47+ CFLAGS="$CFLAGS $PULSE_CFLAGS"],
48+ [true])
49+fi
50+
51+
52 AC_ARG_WITH(oss, AS_HELP_STRING([--without-oss],
53 [Compile without OSS support]))
54
55diff --git a/options.c b/options.c
56--- a/options.c
57+++ b/options.c
58@@ -572,10 +572,11 @@
59
60 #ifdef OPENBSD
61 add_list ("SoundDriver", "SNDIO:JACK:OSS",
62- CHECK_DISCRETE(5), "SNDIO", "Jack", "ALSA", "OSS", "null");
63+ CHECK_DISCRETE(5), "SNDIO", "PulseAudio", "Jack", "ALSA", "OSS", "null");
64+
65 #else
66 add_list ("SoundDriver", "Jack:ALSA:OSS",
67- CHECK_DISCRETE(5), "SNDIO", "Jack", "ALSA", "OSS", "null");
68+ CHECK_DISCRETE(5), "SNDIO", "PulseAudio", "Jack", "ALSA", "OSS", "null");
69 #endif
70
71 add_str ("JackClientName", "moc", CHECK_NONE);
72diff --git a/pulse.c b/pulse.c
73new file mode 100644
74--- /dev/null
75+++ b/pulse.c
76@@ -0,0 +1,705 @@
77+/*
78+ * MOC - music on console
79+ * Copyright (C) 2011 Marien Zwart <marienz@marienz.net>
80+ *
81+ * This program is free software; you can redistribute it and/or modify
82+ * it under the terms of the GNU General Public License as published by
83+ * the Free Software Foundation; either version 2 of the License, or
84+ * (at your option) any later version.
85+ *
86+ */
87+
88+/* PulseAudio backend.
89+ *
90+ * FEATURES:
91+ *
92+ * Does not autostart a PulseAudio server, but uses an already-started
93+ * one, which should be better than alsa-through-pulse.
94+ *
95+ * Supports control of either our stream's or our entire sink's volume
96+ * while we are actually playing. Volume control while paused is
97+ * intentionally unsupported: the PulseAudio documentation strongly
98+ * suggests not passing in an initial volume when creating a stream
99+ * (allowing the server to track this instead), and we do not know
100+ * which sink to control if we do not have a stream open.
101+ *
102+ * IMPLEMENTATION:
103+ *
104+ * Most client-side (resource allocation) errors are fatal. Failure to
105+ * create a server context or stream is not fatal (and MOC should cope
106+ * with these failures too), but server communication failures later
107+ * on are currently not handled (MOC has no great way for us to tell
108+ * it we no longer work, and I am not sure if attempting to reconnect
109+ * is worth it or even a good idea).
110+ *
111+ * The pulse "simple" API is too simple: it combines connecting to the
112+ * server and opening a stream into one operation, while I want to
113+ * connect to the server when MOC starts (and fall back to a different
114+ * backend if there is no server), and I cannot open a stream at that
115+ * time since I do not know the audio format yet.
116+ *
117+ * PulseAudio strongly recommends we use a high-latency connection,
118+ * which the MOC frontend code might not expect from its audio
119+ * backend. We'll see.
120+ *
121+ * We map MOC's percentage volumes linearly to pulse's PA_VOLUME_MUTED
122+ * (0) .. PA_VOLUME_NORM range. This is what the PulseAudio docs recommend
123+ * ( http://pulseaudio.org/wiki/WritingVolumeControlUIs ). It does mean
124+ * PulseAudio volumes above PA_VOLUME_NORM do not work well with MOC.
125+ *
126+ * Comments in audio.h claim "All functions are executed only by one
127+ * thread" (referring to the function in the hw_funcs struct). This is
128+ * a blatant lie. Most of them are invoked off the "output buffer"
129+ * thread (out_buf.c) but at least the "playing" thread (audio.c)
130+ * calls audio_close which calls our close function. We can mostly
131+ * ignore this problem because we serialize on the pulseaudio threaded
132+ * mainloop lock. But it does mean that functions that are normally
133+ * only called between open and close (like reset) are sometimes
134+ * called without us having a stream. Bulletproof, therefore:
135+ * serialize setting/unsetting our global stream using the threaded
136+ * mainloop lock, and check for that stream being non-null before
137+ * using it.
138+ *
139+ * I am not convinced there are no further dragons lurking here: can
140+ * the "playing" thread(s) close and reopen our output stream while
141+ * the "output buffer" thread is sending output there? We can bail if
142+ * our stream is simply closed, but we do not currently detect it
143+ * being reopened and no longer using the same sample format, which
144+ * might have interesting results...
145+ *
146+ * Also, read_mixer is called from the main server thread (handling
147+ * commands). This crashed me once when it got at a stream that was in
148+ * the "creating" state and therefore did not have a valid stream
149+ * index yet. Fixed by only assigning to the stream global when the
150+ * stream is valid.
151+ */
152+
153+#ifdef HAVE_CONFIG_H
154+# include "config.h"
155+#endif
156+
157+#define DEBUG
158+
159+#include <pulse/pulseaudio.h>
160+#include "common.h"
161+#include "log.h"
162+#include "audio.h"
163+
164+
165+/* The pulse mainloop and context are initialized in pulse_init and
166+ * destroyed in pulse_shutdown.
167+ */
168+static pa_threaded_mainloop *mainloop = NULL;
169+static pa_context *context = NULL;
170+
171+/* The stream is initialized in pulse_open and destroyed in pulse_close. */
172+static pa_stream *stream = NULL;
173+
174+static int showing_sink_volume = 0;
175+
176+/* Callbacks that do nothing but wake up the mainloop. */
177+
178+static void context_state_callback (pa_context *context ATTR_UNUSED,
179+ void *userdata)
180+{
181+ pa_threaded_mainloop *m = userdata;
182+
183+ pa_threaded_mainloop_signal (m, 0);
184+}
185+
186+static void stream_state_callback (pa_stream *stream ATTR_UNUSED,
187+ void *userdata)
188+{
189+ pa_threaded_mainloop *m = userdata;
190+
191+ pa_threaded_mainloop_signal (m, 0);
192+}
193+
194+static void stream_write_callback (pa_stream *stream ATTR_UNUSED,
195+ size_t nbytes ATTR_UNUSED, void *userdata)
196+{
197+ pa_threaded_mainloop *m = userdata;
198+
199+ pa_threaded_mainloop_signal (m, 0);
200+}
201+
202+/* Initialize pulse mainloop and context. Failure to connect to the
203+ * pulse daemon is nonfatal, everything else is fatal (as it
204+ * presumably means we ran out of resources).
205+ */
206+static int pulse_init (struct output_driver_caps *caps)
207+{
208+ pa_context *c;
209+ pa_proplist *proplist;
210+
211+ assert (!mainloop);
212+ assert (!context);
213+
214+ mainloop = pa_threaded_mainloop_new ();
215+ if (!mainloop)
216+ fatal ("Cannot create PulseAudio mainloop");
217+
218+ if (pa_threaded_mainloop_start (mainloop) < 0)
219+ fatal ("Cannot start PulseAudio mainloop");
220+
221+ /* TODO: possibly add more props.
222+ *
223+ * There are a few we could set in proplist.h but nothing I
224+ * expect to be very useful.
225+ *
226+ * http://pulseaudio.org/wiki/ApplicationProperties recommends
227+ * setting at least application.name, icon.name and media.role.
228+ *
229+ * No need to set application.name here, the name passed to
230+ * pa_context_new_with_proplist overrides it.
231+ */
232+ proplist = pa_proplist_new ();
233+ if (!proplist)
234+ fatal ("Cannot allocate PulseAudio proplist");
235+
236+ pa_proplist_sets (proplist,
237+ PA_PROP_APPLICATION_VERSION, PACKAGE_VERSION);
238+ pa_proplist_sets (proplist, PA_PROP_MEDIA_ROLE, "music");
239+ pa_proplist_sets (proplist, PA_PROP_APPLICATION_ID, "net.daper.moc");
240+
241+ pa_threaded_mainloop_lock (mainloop);
242+
243+ c = pa_context_new_with_proplist (
244+ pa_threaded_mainloop_get_api (mainloop),
245+ PACKAGE_NAME, proplist);
246+ pa_proplist_free (proplist);
247+
248+ if (!c)
249+ fatal ("Cannot allocate PulseAudio context");
250+
251+ pa_context_set_state_callback (c, context_state_callback, mainloop);
252+
253+ /* Ignore return value, rely on state being set properly */
254+ pa_context_connect (c, NULL, PA_CONTEXT_NOAUTOSPAWN, NULL);
255+
256+ while (1) {
257+ pa_context_state_t state = pa_context_get_state (c);
258+
259+ if (state == PA_CONTEXT_READY)
260+ break;
261+
262+ if (!PA_CONTEXT_IS_GOOD (state)) {
263+ error ("PulseAudio connection failed: %s",
264+ pa_strerror (pa_context_errno (c)));
265+
266+ goto unlock_and_fail;
267+ }
268+
269+ debug ("waiting for context to become ready...");
270+ pa_threaded_mainloop_wait (mainloop);
271+ }
272+
273+ /* Only set the global now that the context is actually ready */
274+ context = c;
275+
276+ pa_threaded_mainloop_unlock (mainloop);
277+
278+ /* We just make up the hardware capabilities, since pulse is
279+ * supposed to be abstracting these out. Assume pulse will
280+ * deal with anything we want to throw at it, and that we will
281+ * only want mono or stereo audio.
282+ */
283+ caps->min_channels = 1;
284+ caps->max_channels = 2;
285+ caps->formats = (SFMT_S8 | SFMT_S16 | SFMT_S32 |
286+ SFMT_FLOAT | SFMT_BE | SFMT_LE);
287+
288+ return 1;
289+
290+unlock_and_fail:
291+
292+ pa_context_unref (c);
293+
294+ pa_threaded_mainloop_unlock (mainloop);
295+
296+ pa_threaded_mainloop_stop (mainloop);
297+ pa_threaded_mainloop_free (mainloop);
298+ mainloop = NULL;
299+
300+ return 0;
301+}
302+
303+static void pulse_shutdown (void)
304+{
305+ pa_threaded_mainloop_lock (mainloop);
306+
307+ pa_context_disconnect (context);
308+ pa_context_unref (context);
309+ context = NULL;
310+
311+ pa_threaded_mainloop_unlock (mainloop);
312+
313+ pa_threaded_mainloop_stop (mainloop);
314+ pa_threaded_mainloop_free (mainloop);
315+ mainloop = NULL;
316+}
317+
318+static int pulse_open (struct sound_params *sound_params)
319+{
320+ pa_sample_spec ss;
321+ pa_buffer_attr ba;
322+ pa_stream *s;
323+
324+ assert (!stream);
325+ /* Initialize everything to -1, which in practice gets us
326+ * about 2 seconds of latency (which is fine). This is not the
327+ * same as passing NULL for this struct, which gets us an
328+ * unnecessarily short alsa-like latency.
329+ */
330+ ba.fragsize = (uint32_t) -1;
331+ ba.tlength = (uint32_t) -1;
332+ ba.prebuf = (uint32_t) -1;
333+ ba.minreq = (uint32_t) -1;
334+ ba.maxlength = (uint32_t) -1;
335+
336+ ss.channels = sound_params->channels;
337+ ss.rate = sound_params->rate;
338+ switch (sound_params->fmt) {
339+ case SFMT_U8:
340+ ss.format = PA_SAMPLE_U8;
341+ break;
342+ case SFMT_S16 | SFMT_LE:
343+ ss.format = PA_SAMPLE_S16LE;
344+ break;
345+ case SFMT_S16 | SFMT_BE:
346+ ss.format = PA_SAMPLE_S16BE;
347+ break;
348+ case SFMT_FLOAT | SFMT_LE:
349+ ss.format = PA_SAMPLE_FLOAT32LE;
350+ break;
351+ case SFMT_FLOAT | SFMT_BE:
352+ ss.format = PA_SAMPLE_FLOAT32BE;
353+ break;
354+ case SFMT_S32 | SFMT_LE:
355+ ss.format = PA_SAMPLE_S32LE;
356+ break;
357+ case SFMT_S32 | SFMT_BE:
358+ ss.format = PA_SAMPLE_S32BE;
359+ break;
360+
361+ default:
362+ fatal ("pulse: got unrequested format");
363+ }
364+
365+ debug ("opening stream");
366+
367+ pa_threaded_mainloop_lock (mainloop);
368+
369+ /* TODO: figure out if there are useful stream properties to set.
370+ *
371+ * I do not really see any in proplist.h that we can set from
372+ * here (there are media title/artist/etc props but we do not
373+ * have that data available here).
374+ */
375+ s = pa_stream_new (context, "music", &ss, NULL);
376+ if (!s)
377+ fatal ("pulse: stream allocation failed");
378+
379+ pa_stream_set_state_callback (s, stream_state_callback, mainloop);
380+ pa_stream_set_write_callback (s, stream_write_callback, mainloop);
381+
382+ /* Ignore return value, rely on failed stream state instead. */
383+ pa_stream_connect_playback (
384+ s, NULL, &ba,
385+ PA_STREAM_INTERPOLATE_TIMING |
386+ PA_STREAM_AUTO_TIMING_UPDATE |
387+ PA_STREAM_ADJUST_LATENCY,
388+ NULL, NULL);
389+
390+ while (1) {
391+ pa_stream_state_t state = pa_stream_get_state (s);
392+
393+ if (state == PA_STREAM_READY)
394+ break;
395+
396+ if (!PA_STREAM_IS_GOOD (state)) {
397+ error ("PulseAudio stream connection failed");
398+
399+ goto fail;
400+ }
401+
402+ debug ("waiting for stream to become ready...");
403+ pa_threaded_mainloop_wait (mainloop);
404+ }
405+
406+ /* Only set the global stream now that it is actually ready */
407+ stream = s;
408+
409+ pa_threaded_mainloop_unlock (mainloop);
410+
411+ return 1;
412+
413+fail:
414+ pa_stream_unref (s);
415+
416+ pa_threaded_mainloop_unlock (mainloop);
417+ return 0;
418+}
419+
420+static void pulse_close (void)
421+{
422+ debug ("closing stream");
423+
424+ pa_threaded_mainloop_lock (mainloop);
425+
426+ pa_stream_disconnect (stream);
427+ pa_stream_unref (stream);
428+ stream = NULL;
429+
430+ pa_threaded_mainloop_unlock (mainloop);
431+}
432+
433+static int pulse_play (const char *buff, const size_t size)
434+{
435+ size_t offset = 0;
436+
437+ debug ("Got %d bytes to play", (int)size);
438+
439+ pa_threaded_mainloop_lock (mainloop);
440+
441+ /* The buffer is usually writable when we get here, and there
442+ * are usually few (if any) writes after the first one. So
443+ * there is no point in doing further writes directly from the
444+ * callback: we can just do all writes from this thread.
445+ */
446+
447+ /* Break out of the loop if some other thread manages to close
448+ * our stream underneath us.
449+ */
450+ while (stream) {
451+ size_t towrite = MIN(pa_stream_writable_size (stream),
452+ size - offset);
453+ debug ("writing %d bytes", (int)towrite);
454+
455+ /* We have no working way of dealing with errors
456+ * (see below). */
457+ if (pa_stream_write(stream, buff + offset, towrite,
458+ NULL, 0, PA_SEEK_RELATIVE))
459+ error ("pa_stream_write failed");
460+
461+ offset += towrite;
462+
463+ if (offset >= size)
464+ break;
465+
466+ pa_threaded_mainloop_wait (mainloop);
467+ }
468+
469+ pa_threaded_mainloop_unlock (mainloop);
470+
471+ debug ("Done playing!");
472+
473+ /* We should always return size, calling code does not deal
474+ * well with anything else. Only read the rest if you want to
475+ * know why.
476+ *
477+ * The output buffer reader thread (out_buf.c:read_thread)
478+ * repeatedly loads some 64k/0.1s of audio into a buffer on
479+ * the stack, then calls audio_send_pcm repeatedly until this
480+ * entire buffer has been processed (similar to the loop in
481+ * this function). audio_send_pcm applies the softmixer and
482+ * equalizer, then feeds the result to this function, passing
483+ * through our return value.
484+ *
485+ * So if we return less than size the equalizer/softmixer is
486+ * re-applied to the remaining data, which is silly. Also,
487+ * audio_send_pcm checks for our return value being zero and
488+ * calls fatal() if it is, so try to always process *some*
489+ * data. Also, out_buf.c uses the return value of this
490+ * function from the last run through its inner loop to update
491+ * its time attribute, which means it will be interestingly
492+ * off if that loop ran more than once.
493+ *
494+ * Oh, and alsa.c seems to think it can return -1 to indicate
495+ * failure, which will cause out_buf.c to rewind its buffer
496+ * (to before its start, usually).
497+ */
498+ return size;
499+}
500+
501+static void volume_cb (const pa_cvolume *v, void *userdata)
502+{
503+ int *result = userdata;
504+
505+ if (v)
506+ *result = 100 * pa_cvolume_avg (v) / PA_VOLUME_NORM;
507+
508+ pa_threaded_mainloop_signal (mainloop, 0);
509+}
510+
511+static void sink_volume_cb (pa_context *c ATTR_UNUSED,
512+ const pa_sink_info *i, int eol ATTR_UNUSED,
513+ void *userdata)
514+{
515+ volume_cb (i ? &i->volume : NULL, userdata);
516+}
517+
518+static void sink_input_volume_cb (pa_context *c ATTR_UNUSED,
519+ const pa_sink_input_info *i,
520+ int eol ATTR_UNUSED,
521+ void *userdata ATTR_UNUSED)
522+{
523+ volume_cb (i ? &i->volume : NULL, userdata);
524+}
525+
526+static int pulse_read_mixer (void)
527+{
528+ pa_operation *op;
529+ int result = 0;
530+
531+ debug ("read mixer");
532+
533+ pa_threaded_mainloop_lock (mainloop);
534+
535+ if (stream) {
536+ if (showing_sink_volume)
537+ op = pa_context_get_sink_info_by_index (
538+ context, pa_stream_get_device_index (stream),
539+ sink_volume_cb, &result);
540+ else
541+ op = pa_context_get_sink_input_info (
542+ context, pa_stream_get_index (stream),
543+ sink_input_volume_cb, &result);
544+
545+ while (pa_operation_get_state (op) == PA_OPERATION_RUNNING)
546+ pa_threaded_mainloop_wait (mainloop);
547+
548+ pa_operation_unref (op);
549+ }
550+
551+ pa_threaded_mainloop_unlock (mainloop);
552+
553+ return result;
554+}
555+
556+static void pulse_set_mixer (int vol)
557+{
558+ pa_cvolume v;
559+ pa_operation *op;
560+
561+ /* Setting volume for one channel does the right thing. */
562+ pa_cvolume_set(&v, 1, vol * PA_VOLUME_NORM / 100);
563+
564+ pa_threaded_mainloop_lock (mainloop);
565+
566+ if (stream) {
567+ if (showing_sink_volume)
568+ op = pa_context_set_sink_volume_by_index (
569+ context, pa_stream_get_device_index (stream),
570+ &v, NULL, NULL);
571+ else
572+ op = pa_context_set_sink_input_volume (
573+ context, pa_stream_get_index (stream),
574+ &v, NULL, NULL);
575+
576+ pa_operation_unref (op);
577+ }
578+
579+ pa_threaded_mainloop_unlock (mainloop);
580+}
581+
582+static int pulse_get_buff_fill (void)
583+{
584+ /* This function is problematic. MOC uses it to for the "time
585+ * remaining" in the UI, but calls it more than once per
586+ * second (after each chunk of audio played, not for each
587+ * playback time update). We have to be fairly accurate here
588+ * for that time remaining to not jump weirdly. But PulseAudio
589+ * cannot give us a 100% accurate value here, as it involves a
590+ * server roundtrip. And if we call this a lot it suggests
591+ * switching to a mode where the value is interpolated, making
592+ * it presumably more inaccurate (see the flags we pass to
593+ * pa_stream_connect_playback).
594+ *
595+ * MOC also contains what I believe to be a race: it calls
596+ * audio_get_buff_fill "soon" (after playing the first chunk)
597+ * after starting playback of the next song, at which point we
598+ * still have part of the previous song buffered. This means
599+ * our position into the new song is negative, which fails an
600+ * assert (in out_buf.c:out_buf_time_get). There is no sane
601+ * way for us to detect this condition. I believe no other
602+ * backend triggers this because the assert sits after an
603+ * implicit float -> int seconds conversion, which means we
604+ * have to be off by at least an entire second to get a
605+ * negative value, and none of the other backends have buffers
606+ * that large (alsa buffers are supposedly a few 100 ms).
607+ */
608+ pa_usec_t buffered_usecs = 0;
609+ int buffered_bytes = 0;
610+
611+ pa_threaded_mainloop_lock (mainloop);
612+
613+ /* Using pa_stream_get_timing_info and returning the distance
614+ * between write_index and read_index would be more obvious,
615+ * but because of how the result is actually used I believe
616+ * using the latency value is slightly more correct, and it
617+ * makes the following crash-avoidance hack more obvious.
618+ */
619+
620+ /* This function will frequently fail the first time we call
621+ * it (pulse does not have the requested data yet). We ignore
622+ * that and just return 0.
623+ *
624+ * Deal with stream being NULL too, just in case this is
625+ * called in a racy fashion similar to how reset() is.
626+ */
627+ if (stream &&
628+ pa_stream_get_latency (stream, &buffered_usecs, NULL) >= 0) {
629+ /* Crash-avoidance HACK: floor our latency to at most
630+ * 1 second. It is usually more, but reporting that at
631+ * the start of playback crashes MOC, and we cannot
632+ * sanely detect when reporting it is safe.
633+ */
634+ if (buffered_usecs > 1000000)
635+ buffered_usecs = 1000000;
636+
637+ buffered_bytes = pa_usec_to_bytes (
638+ buffered_usecs,
639+ pa_stream_get_sample_spec (stream));
640+ }
641+
642+ pa_threaded_mainloop_unlock (mainloop);
643+
644+ debug ("buffer fill: %d usec / %d bytes",
645+ (int) buffered_usecs, (int) buffered_bytes);
646+
647+ return buffered_bytes;
648+}
649+
650+static void flush_callback (pa_stream *s ATTR_UNUSED, int success,
651+ void *userdata)
652+{
653+ int *result = userdata;
654+
655+ *result = success;
656+
657+ pa_threaded_mainloop_signal (mainloop, 0);
658+}
659+
660+static int pulse_reset (void)
661+{
662+ pa_operation *op;
663+ int result = 0;
664+
665+ debug ("reset requested");
666+
667+ pa_threaded_mainloop_lock (mainloop);
668+
669+ /* We *should* have a stream here, but MOC is racy, so bulletproof */
670+ if (stream) {
671+ op = pa_stream_flush (stream, flush_callback, &result);
672+
673+ while (pa_operation_get_state (op) == PA_OPERATION_RUNNING)
674+ pa_threaded_mainloop_wait (mainloop);
675+
676+ pa_operation_unref (op);
677+ } else
678+ logit ("pulse_reset() called without a stream");
679+
680+ pa_threaded_mainloop_unlock (mainloop);
681+
682+ return result;
683+}
684+
685+static int pulse_get_rate (void)
686+{
687+ /* This is called once right after open. Do not bother making
688+ * this fast. */
689+
690+ int result;
691+
692+ pa_threaded_mainloop_lock (mainloop);
693+
694+ if (stream)
695+ result = pa_stream_get_sample_spec (stream)->rate;
696+ else {
697+ error ("get_rate called without a stream");
698+ result = 0;
699+ }
700+
701+ pa_threaded_mainloop_unlock (mainloop);
702+
703+ return result;
704+}
705+
706+static void pulse_toggle_mixer_channel (void)
707+{
708+ showing_sink_volume = !showing_sink_volume;
709+}
710+
711+static void sink_name_cb (pa_context *c ATTR_UNUSED,
712+ const pa_sink_info *i, int eol ATTR_UNUSED,
713+ void *userdata)
714+{
715+ char **result = userdata;
716+
717+ if (i && !*result)
718+ *result = xstrdup (i->name);
719+
720+ pa_threaded_mainloop_signal (mainloop, 0);
721+}
722+
723+static void sink_input_name_cb (pa_context *c ATTR_UNUSED,
724+ const pa_sink_input_info *i,
725+ int eol ATTR_UNUSED,
726+ void *userdata)
727+{
728+ char **result = userdata;
729+
730+ if (i && !*result)
731+ *result = xstrdup (i->name);
732+
733+ pa_threaded_mainloop_signal (mainloop, 0);
734+}
735+
736+static char *pulse_get_mixer_channel_name (void)
737+{
738+ char *result = NULL;
739+ pa_operation *op;
740+
741+ pa_threaded_mainloop_lock (mainloop);
742+
743+ if (stream) {
744+ if (showing_sink_volume)
745+ op = pa_context_get_sink_info_by_index (
746+ context, pa_stream_get_device_index (stream),
747+ sink_name_cb, &result);
748+ else
749+ op = pa_context_get_sink_input_info (
750+ context, pa_stream_get_index (stream),
751+ sink_input_name_cb, &result);
752+
753+ while (pa_operation_get_state (op) == PA_OPERATION_RUNNING)
754+ pa_threaded_mainloop_wait (mainloop);
755+
756+ pa_operation_unref (op);
757+ }
758+
759+ pa_threaded_mainloop_unlock (mainloop);
760+
761+ if (!result)
762+ result = xstrdup ("disconnected");
763+
764+ return result;
765+}
766+
767+void pulse_funcs (struct hw_funcs *funcs)
768+{
769+ funcs->init = pulse_init;
770+ funcs->shutdown = pulse_shutdown;
771+ funcs->open = pulse_open;
772+ funcs->close = pulse_close;
773+ funcs->play = pulse_play;
774+ funcs->read_mixer = pulse_read_mixer;
775+ funcs->set_mixer = pulse_set_mixer;
776+ funcs->get_buff_fill = pulse_get_buff_fill;
777+ funcs->reset = pulse_reset;
778+ funcs->get_rate = pulse_get_rate;
779+ funcs->toggle_mixer_channel = pulse_toggle_mixer_channel;
780+ funcs->get_mixer_channel_name = pulse_get_mixer_channel_name;
781+}
782diff --git a/pulse.h b/pulse.h
783new file mode 100644
784--- /dev/null
785+++ b/pulse.h
786@@ -0,0 +1,14 @@
787+#ifndef PULSE_H
788+#define PULSE_H
789+
790+#ifdef __cplusplus
791+extern "C" {
792+#endif
793+
794+void pulse_funcs (struct hw_funcs *funcs);
795+
796+#ifdef __cplusplus
797+}
798+#endif
799+
800+#endif