Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
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