From 16ebfd499e495a548033b45326d37e2cb681bdcf Mon Sep 17 00:00:00 2001 From: Anton Novikov Date: Mon, 8 Feb 2010 21:56:57 +0400 Subject: Added PulseAudio support --- plugins/pulse/Makefile.am | 7 ++ plugins/pulse/pulse.c | 315 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 322 insertions(+) create mode 100644 plugins/pulse/Makefile.am create mode 100644 plugins/pulse/pulse.c (limited to 'plugins/pulse') 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 + + 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 . + + PulseAudio output plugin + Copyright (C) 2010 Anton Novikov +*/ + +#ifdef HAVE_CONFIG_H +# include "../../config.h" +#endif + +#include + +#include +#include + +#ifdef __linux__ + #include +#endif + +#include +#include +#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, +}; -- cgit v1.2.3