summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans-Nikolai Viessmann2017-03-26 18:10:47 +0100
committerHans-Nikolai Viessmann2017-03-26 18:10:47 +0100
commit4f6b67ce298692ab921844f58b92cf2e84f0fa55 (patch)
tree621f86d01c92feda49f298f91aca2f9f330bb99d
downloadaur-4f6b67ce298692ab921844f58b92cf2e84f0fa55.tar.gz
Initial commit
-rw-r--r--.SRCINFO39
-rw-r--r--0001-Pulseaudio-backend.patch840
-rw-r--r--PKGBUILD59
3 files changed, 938 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO
new file mode 100644
index 000000000000..0b9b49b6ea1e
--- /dev/null
+++ b/.SRCINFO
@@ -0,0 +1,39 @@
+pkgbase = moc-pulse-svn
+ pkgdesc = An ncurses console audio player with support for pulseaudio (SVN)
+ pkgver = r2947
+ pkgrel = 1
+ url = http://moc.daper.net
+ arch = i686
+ arch = x86_64
+ license = GPL
+ makedepends = speex
+ makedepends = ffmpeg
+ makedepends = taglib
+ makedepends = libmpcdec
+ makedepends = wavpack
+ makedepends = libmodplug
+ makedepends = subversion
+ makedepends = faad2
+ depends = libmad
+ depends = libid3tag
+ depends = jack
+ depends = curl
+ depends = libltdl
+ depends = file
+ optdepends = speex: for using the speex plugin
+ optdepends = ffmpeg: for using the ffmpeg plugin
+ optdepends = taglib: for using the musepack plugin
+ optdepends = libmpcdec: for using the musepack plugin
+ optdepends = wavpack: for using the wavpack plugin
+ optdepends = libmodplug: for using the modplug plugin
+ optdepends = faad2: for use the aac plugin
+ provides = moc
+ conflicts = moc
+ options = !libtool
+ source = moc-pulse-svn::svn://daper.net/moc/trunk
+ source = 0001-Pulseaudio-backend.patch
+ sha1sums = SKIP
+ sha1sums = d86a04606eaa0960f3b59626070cd231d37b6430
+
+pkgname = moc-pulse-svn
+
diff --git a/0001-Pulseaudio-backend.patch b/0001-Pulseaudio-backend.patch
new file mode 100644
index 000000000000..76277f9d40e7
--- /dev/null
+++ b/0001-Pulseaudio-backend.patch
@@ -0,0 +1,840 @@
+From 61f9ecdfb20787e916a871c9632f6d375de3242c Mon Sep 17 00:00:00 2001
+From: Vladimir Protasov <eoranged@ya.ru>
+Date: Wed, 23 Jul 2014 01:47:39 +0400
+Subject: [PATCH 1/3] Pulseaudio backend.
+
+http://moc.daper.net/node/831
+Thanks for marienz.
+
+Signed-off-by: Hans-Nikolai Viessmann <hv15@hw.ac.uk>
+---
+ audio.c | 12 +
+ configure.in | 15 ++
+ options.c | 5 +-
+ pulse.c | 705 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ pulse.h | 14 ++
+ 5 files changed, 749 insertions(+), 2 deletions(-)
+ create mode 100644 pulse.c
+ create mode 100644 pulse.h
+
+diff --git a/audio.c b/audio.c
+index 57f6a97..839cc8a 100644
+--- 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
+@@ -887,6 +890,15 @@ static void find_working_driver (lists_t_strs *drivers, struct hw_funcs *funcs)
+ }
+ #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/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -345,6 +345,9 @@
+ #ifdef HAVE_SNDIO
+ printf (" SNDIO");
+ #endif
++#ifdef HAVE_PULSE
++ printf(" PULSE");
++#endif
+ #ifdef HAVE_ALSA
+ printf (" ALSA");
+ #endif
+diff --git a/configure.in b/configure.in
+index 974403e..77432c8 100644
+--- a/configure.in
++++ b/configure.in
+@@ -157,6 +157,21 @@ then
+ [#include <db.h>]])
+ 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
+index 3ec4a28..496b1f8 100644
+--- a/options.c
++++ b/options.c
+@@ -567,10 +567,11 @@ void options_init ()
+
+ #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
+index 0000000..d5724dd
+--- /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
+index 0000000..7f99067
+--- /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
+--
+2.12.1
+
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 000000000000..45a9dfdbc055
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,59 @@
+#Maintainer: Hans-Nikolai Viessmann <hv15 AT hw.ac.uk>
+#Contributor: Øyvind 'MrElendig' Heggstad <mrelendig@har-ikkje.net>
+#Contributor: jsteel <mail at jsteel dot org>
+
+_pkgname=moc
+pkgname="${_pkgname}-pulse-svn"
+pkgver=r2947
+pkgrel=1
+pkgdesc='An ncurses console audio player with support for pulseaudio (SVN)'
+url='http://moc.daper.net'
+arch=('i686' 'x86_64')
+license=('GPL')
+depends=('libmad' 'libid3tag' 'jack' 'curl' 'libltdl' 'file')
+makedepends=('speex' 'ffmpeg' 'taglib' 'libmpcdec' 'wavpack'
+ 'libmodplug' 'subversion' 'faad2')
+optdepends=('speex: for using the speex plugin'
+ 'ffmpeg: for using the ffmpeg plugin'
+ 'taglib: for using the musepack plugin'
+ 'libmpcdec: for using the musepack plugin'
+ 'wavpack: for using the wavpack plugin'
+ 'libmodplug: for using the modplug plugin'
+ 'faad2: for use the aac plugin')
+conflicts=('moc')
+provides=('moc')
+options=('!libtool')
+source=("${pkgname}::svn://daper.net/moc/trunk"
+ '0001-Pulseaudio-backend.patch')
+sha1sums=('SKIP'
+ 'd86a04606eaa0960f3b59626070cd231d37b6430')
+
+pkgver() {
+ cd "$srcdir/$pkgname"
+ local ver="$(svnversion)"
+ printf "r%s" "${ver//[[:alpha:]]}"
+}
+
+prepare() {
+ cd "$srcdir/$pkgname"
+ # Add pulseaudio backend
+ patch -p1 -i ../0001-Pulseaudio-backend.patch
+
+ # re-configure
+ autoreconf -f -i -Wall,no-obsolete
+}
+
+build() {
+ cd "$srcdir/$pkgname"
+ ./configure --prefix=/usr --without-rcc \
+ --with-alsa --with-oss --with-jack --with-aac --with-mp3 \
+ --with-musepack --with-vorbis --with-flac --with-wavpack \
+ --with-sndfile --with-modplug --with-ffmpeg --with-speex \
+ --with-samplerate --with-curl --disable-cache --disable-debug
+ make
+}
+
+package() {
+ cd "$srcdir/$pkgname"
+ make DESTDIR="$pkgdir" install
+}