diff options
-rw-r--r-- | Makefile.am | 3 | ||||
-rw-r--r-- | configure.ac | 10 | ||||
-rw-r--r-- | plugins/pulse/Makefile.am | 7 | ||||
-rw-r--r-- | plugins/pulse/pulse.c | 315 |
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, +}; |