pulseaudio.patch 22 KB

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