summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Anton Novikov <tonn.post@gmail.com>2010-02-08 21:56:57 +0400
committerGravatar Alexey Yakovenko <wakeroid@gmail.com>2010-02-08 19:23:18 +0100
commit16ebfd499e495a548033b45326d37e2cb681bdcf (patch)
treeb5dc1f7dca2c4de96c137e3ae26a1622f3db6d80
parentd7644e273f412f9e6fa5dff8d39daba1e8be5228 (diff)
Added PulseAudio support
-rw-r--r--Makefile.am3
-rw-r--r--configure.ac10
-rw-r--r--plugins/pulse/Makefile.am7
-rw-r--r--plugins/pulse/pulse.c315
4 files changed, 334 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am
index 1f2df71e..d47ef33a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -19,7 +19,8 @@ SUBDIRS = ${MPGMAD_DIR}\
${SNDFILE_DIR}\
${WAVPACK_DIR}\
${CDDA_DIR}\
- ${OSS_DIR}
+ ${OSS_DIR}\
+ ${PULSE_DIR}
dumbpath=@top_srcdir@/dumb
gmepath=@top_srcdir@/gme/Game_Music_Emu-0.5.2
diff --git a/configure.ac b/configure.ac
index 9af176be..15b1c5a8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -48,6 +48,7 @@ PKG_CHECK_MODULES(DEPS, samplerate)
PKG_CHECK_MODULES(GTKUI_DEPS, gtk+-2.0 >= 2.12 gthread-2.0 glib-2.0, HAVE_GTK=1, NO_GTK=1)
PKG_CHECK_MODULES(ALSA_DEPS, alsa, HAVE_ALSA=1, NO_ALSA=1)
PKG_CHECK_MODULES(FFMPEG_DEPS, libavcodec >= 51.0.0 libavutil libavformat >= 52.0.0, HAVE_FFMPEG=1, NO_FFMPEG=1)
+PKG_CHECK_MODULES(PULSE_DEPS, libpulse-simple, HAVE_PULSE=1, NO_PULSE=1)
AC_ARG_ENABLE(libnotify, [ --enable-libnotify Enable support for OSD notifications], [libnotify=yes])
@@ -253,6 +254,13 @@ dnl echo "OSS_LIBS=$OSS_LIBS"
dnl echo "OSS_DIR=$OSS_DIR"
dnl echo
+AM_CONDITIONAL(HAVE_PULSE, test $HAVE_PULSE)
+if test ${HAVE_PULSE}; then
+ PULSE_DIR="plugins/pulse"
+ AC_SUBST(PULSE_DEPS_CFLAGS)
+ AC_SUBST(PULSE_DEPS_LIBS)
+ AC_SUBST(PULSE_DIR)
+fi
dnl print summary
echo
@@ -292,6 +300,7 @@ PRINT_PLUGIN_INFO([hotkeys],[Global hotkeys support],[test "x$HAVE_XLIB_H" = "xy
PRINT_PLUGIN_INFO([libnotify],[Current track notification],[test $HAVE_NOTIFY])
PRINT_PLUGIN_INFO([ffmpeg],[ffmpeg codecs],[test $HAVE_FFMPEG])
PRINT_PLUGIN_INFO([oss],[oss output plugin],[test "x$have_oss" = "xyes"])
+PRINT_PLUGIN_INFO([pulse],[PulseAudio output plugin],[test $HAVE_PULSE])
echo
AC_OUTPUT([
@@ -320,6 +329,7 @@ plugins/adplug/Makefile
plugins/ffmpeg/Makefile
plugins/sid/Makefile
plugins/oss/Makefile
+plugins/pulse/Makefile
deadbeef.desktop
])
diff --git a/plugins/pulse/Makefile.am b/plugins/pulse/Makefile.am
new file mode 100644
index 00000000..c04bdb81
--- /dev/null
+++ b/plugins/pulse/Makefile.am
@@ -0,0 +1,7 @@
+pulsedir = $(libdir)/$(PACKAGE)
+pkglib_LTLIBRARIES = pulse.la
+AM_CFLAGS = $(CFLAGS) $(PULSE_DEPS_CFLAGS) -std=c99
+pulse_la_SOURCES = pulse.c
+pulse_la_LDFLAGS = -module
+pulse_la_LIBADD = $(LDADD) $(PULSE_DEPS_LIBS)
+
diff --git a/plugins/pulse/pulse.c b/plugins/pulse/pulse.c
new file mode 100644
index 00000000..a359bf19
--- /dev/null
+++ b/plugins/pulse/pulse.c
@@ -0,0 +1,315 @@
+/*
+ DeaDBeeF - ultimate music player for GNU/Linux systems with X11
+ Copyright (C) 2009-2010 Alexey Yakovenko <waker@users.sourceforge.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.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ PulseAudio output plugin
+ Copyright (C) 2010 Anton Novikov <tonn.post@gmail.com>
+*/
+
+#ifdef HAVE_CONFIG_H
+# include "../../config.h"
+#endif
+
+#include <pulse/simple.h>
+
+#include <stdint.h>
+#include <unistd.h>
+
+#ifdef __linux__
+ #include <sys/prctl.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include "../../deadbeef.h"
+
+#define trace(...) { fprintf(stdout, __VA_ARGS__); }
+//#define trace(fmt,...)
+
+static DB_output_t plugin;
+DB_functions_t * deadbeef;
+
+#define CONFSTR_PULSE_SAMPLERATE "pulse.samplerate"
+#define CONFSTR_PULSE_SERVERADDR "pulse.serveraddr"
+#define CONFSTR_PULSE_BUFFERSIZE "pulse.buffersize"
+
+static intptr_t pulse_tid;
+static int pulse_terminate;
+
+static pa_simple *s;
+static pa_sample_spec ss;
+static int state;
+static uintptr_t mutex;
+
+static int buffer_size;
+
+static void pulse_thread(void *context);
+
+static void pulse_callback(char *stream, int len);
+
+static int pulse_init(void)
+{
+ trace ("pulse_init\n");
+ state = OUTPUT_STATE_STOPPED;
+ pulse_terminate = 0;
+
+ // Read serveraddr from config
+ const char * server = deadbeef->conf_get_str(CONFSTR_PULSE_SERVERADDR, NULL);
+
+ if (server)
+ server = strcmp(server, "default") ? server : NULL;
+
+ // Read samplerate from config
+ ss.rate = deadbeef->conf_get_int(CONFSTR_PULSE_SAMPLERATE, 44100);
+ trace ("pulse: samplerate: %d\n", ss.rate);
+
+ // TODO: add config for this
+ pa_channel_map * map = NULL;//pa_channel_map_init_stereo(NULL);
+ ss.channels = 2;
+ ss.format = PA_SAMPLE_S16NE;
+
+ // TODO: where list of all available devices? add this option to config too..
+ char * dev = NULL;
+
+ pa_buffer_attr * attr = NULL;
+ //attr->maxlength = Maximum length of the buffer.
+ //attr->tlength = Playback only: target length of the buffer.
+ //attr->prebuf = Playback only: pre-buffering.
+ //attr->minreq = Playback only: minimum request.
+ //attr->fragsize = Recording only: fragment size.
+
+ buffer_size = deadbeef->conf_get_int(CONFSTR_PULSE_BUFFERSIZE, 4096);
+
+ int error;
+ s = pa_simple_new(server, "Deadbeef", PA_STREAM_PLAYBACK, dev, "Music", &ss, map, attr, &error);
+
+ if (!s)
+ {
+ return -1;
+ }
+
+ pulse_tid = deadbeef->thread_start(pulse_thread, NULL);
+
+ return 0;
+}
+
+static int pulse_free(void)
+{
+ trace("pulse_free\n");
+
+ if (pulse_tid)
+ {
+ pulse_terminate = 1;
+ deadbeef->thread_join(pulse_tid);
+ }
+
+ pulse_tid = 0;
+ state = OUTPUT_STATE_STOPPED;
+ pa_simple_free(s);
+ s = NULL;
+
+ return 0;
+}
+
+static int pulse_play(void)
+{
+ if (!pulse_tid)
+ {
+ if (pulse_init () < 0)
+ {
+ return -1;
+ }
+ }
+
+ state = OUTPUT_STATE_PLAYING;
+ return 0;
+}
+
+static int pulse_stop(void)
+{
+ state = OUTPUT_STATE_STOPPED;
+ deadbeef->streamer_reset(1);
+ return 0;
+}
+
+static int pulse_pause(void)
+{
+ if (state == OUTPUT_STATE_STOPPED)
+ {
+ return -1;
+ }
+
+ state = OUTPUT_STATE_PAUSED;
+
+ return 0;
+}
+
+static int pulse_unpause(void)
+{
+ if (state == OUTPUT_STATE_PAUSED)
+ {
+ state = OUTPUT_STATE_PLAYING;
+ }
+
+ return 0;
+}
+
+static int pulse_change_rate(int rate)
+{
+ pulse_free();
+ ss.rate = rate;
+
+ if (!pulse_init())
+ return -1;
+
+ return ss.rate;
+}
+
+static int pulse_get_rate(void)
+{
+ return ss.rate;
+}
+
+static int pulse_get_bps(void)
+{
+ return 16;
+}
+
+static int pulse_get_channels(void)
+{
+ return ss.channels;
+}
+
+static int pulse_get_endianness(void)
+{
+#if WORDS_BIGENDIAN
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+static void pulse_thread(void *context)
+{
+#ifdef __linux__
+ prctl(PR_SET_NAME, "deadbeef-pulse", 0, 0, 0, 0);
+#endif
+
+ while (!pulse_terminate)
+ {
+ if (state != OUTPUT_STATE_PLAYING)
+ {
+ usleep(1000);
+ }
+ else
+ {
+ char buf[buffer_size];
+ pulse_callback (buf, sizeof (buf));
+ int error;
+
+ deadbeef->mutex_lock(mutex);
+ int res = pa_simple_write(s, buf, sizeof (buf), &error);
+ deadbeef->mutex_unlock(mutex);
+
+ if (res < 0)
+ {
+ fprintf(stderr, "pulse: failed to write buffer\n");
+ pulse_terminate = 1;
+ }
+ }
+ }
+}
+
+static void pulse_callback(char *stream, int len)
+{
+ if (!deadbeef->streamer_ok_to_read (len))
+ {
+ memset (stream, 0, len);
+ return;
+ }
+
+ int bytesread = deadbeef->streamer_read(stream, len);
+ int16_t ivolume = deadbeef->volume_get_amp() * 1000;
+
+ for (int i = 0; i < bytesread/2; i++)
+ {
+ ((int16_t*)stream)[i] = (int16_t)(((int32_t)(((int16_t*)stream)[i])) * ivolume / 1000);
+ }
+
+ if (bytesread < len)
+ {
+ memset (stream + bytesread, 0, len-bytesread);
+ }
+}
+
+static int pulse_get_state(void)
+{
+ return state;
+}
+
+static int pulse_plugin_start(void)
+{
+ mutex = deadbeef->mutex_create();
+
+ return 0;
+}
+
+static int pulse_plugin_stop(void)
+{
+ deadbeef->mutex_free(mutex);
+
+ return 0;
+}
+
+DB_plugin_t * pulse_load(DB_functions_t *api)
+{
+ deadbeef = api;
+ return DB_PLUGIN (&plugin);
+}
+
+static const char settings_dlg[] =
+ "property \"PulseAudio server\" entry " CONFSTR_PULSE_SERVERADDR " default;\n"
+ "property \"Preferred buffer size\" entry " CONFSTR_PULSE_BUFFERSIZE " 4096;\n"
+ "property \"Samplerate\" entry " CONFSTR_PULSE_SAMPLERATE " 44100;\n";
+
+static DB_output_t plugin =
+{
+ DB_PLUGIN_SET_API_VERSION
+ .plugin.version_major = 0,
+ .plugin.version_minor = 1,
+ .plugin.nostop = 0,
+ .plugin.type = DB_PLUGIN_OUTPUT,
+ .plugin.name = "PulseAudio output plugin",
+ .plugin.descr = "plays sound via pulse API",
+ .plugin.author = "Anton Novikov",
+ .plugin.email = "tonn.post@gmail.com",
+ .plugin.website = "http://deadbeef.sf.net",
+ .plugin.start = pulse_plugin_start,
+ .plugin.stop = pulse_plugin_stop,
+ .plugin.configdialog = settings_dlg,
+ .init = pulse_init,
+ .free = pulse_free,
+ .change_rate = pulse_change_rate,
+ .play = pulse_play,
+ .stop = pulse_stop,
+ .pause = pulse_pause,
+ .unpause = pulse_unpause,
+ .state = pulse_get_state,
+ .samplerate = pulse_get_rate,
+ .bitspersample = pulse_get_bps,
+ .channels = pulse_get_channels,
+ .endianness = pulse_get_endianness,
+};