Reorganised the source tree. We now have src/ with a couple of subdirs:

* daemon/ - Contains the files specific to the polypaudio daemon.
 * modules/ - All loadable modules.
 * polyp/ - Files that are part of the public, application interface or
   are only used in libpolyp.
 * polypcore/ - All other shared files.
 * tests/ - Test programs.
 * utils/ - Utility programs.


git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@487 fefdeb5f-60dc-0310-8127-8f9354f1896f
This commit is contained in:
Pierre Ossman 2006-02-16 19:19:58 +00:00
parent 5b881e6228
commit e205b25d65
246 changed files with 724 additions and 689 deletions

View file

@ -0,0 +1,292 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <assert.h>
#include <stdio.h>
#ifdef HAVE_SYS_POLL_H
#include <sys/poll.h>
#else
#include "poll.h"
#endif
#include <asoundlib.h>
#include <polypcore/core.h>
#include <polypcore/module.h>
#include <polypcore/memchunk.h>
#include <polypcore/sink.h>
#include <polypcore/modargs.h>
#include <polypcore/util.h>
#include <polypcore/sample-util.h>
#include <polypcore/alsa-util.h>
#include <polypcore/xmalloc.h>
#include <polypcore/log.h>
#include "module-alsa-sink-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("ALSA Sink")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("sink_name=<name for the sink> device=<ALSA device> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
struct userdata {
snd_pcm_t *pcm_handle;
pa_sink *sink;
pa_io_event **io_events;
unsigned n_io_events;
size_t frame_size, fragment_size;
pa_memchunk memchunk, silence;
pa_module *module;
};
static const char* const valid_modargs[] = {
"device",
"sink_name",
"format",
"channels",
"rate",
"fragments",
"fragment_size",
NULL
};
#define DEFAULT_SINK_NAME "alsa_output"
#define DEFAULT_DEVICE "default"
static void update_usage(struct userdata *u) {
pa_module_set_used(u->module,
(u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
(u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0));
}
static void xrun_recovery(struct userdata *u) {
assert(u);
pa_log(__FILE__": *** ALSA-XRUN (playback) ***\n");
if (snd_pcm_prepare(u->pcm_handle) < 0)
pa_log(__FILE__": snd_pcm_prepare() failed\n");
}
static void do_write(struct userdata *u) {
assert(u);
update_usage(u);
for (;;) {
pa_memchunk *memchunk = NULL;
snd_pcm_sframes_t frames;
if (u->memchunk.memblock)
memchunk = &u->memchunk;
else {
if (pa_sink_render(u->sink, u->fragment_size, &u->memchunk) < 0)
memchunk = &u->silence;
else
memchunk = &u->memchunk;
}
assert(memchunk->memblock && memchunk->memblock->data && memchunk->length && memchunk->memblock->length && (memchunk->length % u->frame_size) == 0);
if ((frames = snd_pcm_writei(u->pcm_handle, (uint8_t*) memchunk->memblock->data + memchunk->index, memchunk->length / u->frame_size)) < 0) {
if (frames == -EAGAIN)
return;
if (frames == -EPIPE) {
xrun_recovery(u);
continue;
}
pa_log(__FILE__": snd_pcm_writei() failed\n");
return;
}
if (memchunk == &u->memchunk) {
size_t l = frames * u->frame_size;
memchunk->index += l;
memchunk->length -= l;
if (memchunk->length == 0) {
pa_memblock_unref(memchunk->memblock);
memchunk->memblock = NULL;
memchunk->index = memchunk->length = 0;
}
}
break;
}
}
static void io_callback(pa_mainloop_api*a, pa_io_event *e, PA_GCC_UNUSED int fd, PA_GCC_UNUSED pa_io_event_flags_t f, void *userdata) {
struct userdata *u = userdata;
assert(u && a && e);
if (snd_pcm_state(u->pcm_handle) == SND_PCM_STATE_XRUN)
xrun_recovery(u);
do_write(u);
}
static pa_usec_t sink_get_latency_cb(pa_sink *s) {
pa_usec_t r = 0;
struct userdata *u = s->userdata;
snd_pcm_sframes_t frames;
assert(s && u && u->sink);
if (snd_pcm_delay(u->pcm_handle, &frames) < 0) {
pa_log(__FILE__": failed to get delay\n");
s->get_latency = NULL;
return 0;
}
if (frames < 0)
frames = 0;
r += pa_bytes_to_usec(frames * u->frame_size, &s->sample_spec);
if (u->memchunk.memblock)
r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec);
return r;
}
int pa__init(pa_core *c, pa_module*m) {
pa_modargs *ma = NULL;
int ret = -1;
struct userdata *u = NULL;
const char *dev;
pa_sample_spec ss;
uint32_t periods, fragsize;
snd_pcm_uframes_t period_size;
size_t frame_size;
int err;
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments\n");
goto fail;
}
ss = c->default_sample_spec;
if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
pa_log(__FILE__": failed to parse sample specification\n");
goto fail;
}
frame_size = pa_frame_size(&ss);
periods = 8;
fragsize = 1024;
if (pa_modargs_get_value_u32(ma, "fragments", &periods) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &fragsize) < 0) {
pa_log(__FILE__": failed to parse buffer metrics\n");
goto fail;
}
period_size = fragsize;
u = pa_xmalloc0(sizeof(struct userdata));
m->userdata = u;
u->module = m;
snd_config_update_free_global();
if ((err = snd_pcm_open(&u->pcm_handle, dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) {
pa_log(__FILE__": Error opening PCM device %s: %s\n", dev, snd_strerror(err));
goto fail;
}
if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &periods, &period_size)) < 0) {
pa_log(__FILE__": Failed to set hardware parameters: %s\n", snd_strerror(err));
goto fail;
}
u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
assert(u->sink);
u->sink->get_latency = sink_get_latency_cb;
u->sink->userdata = u;
pa_sink_set_owner(u->sink, m);
u->sink->description = pa_sprintf_malloc("Advanced Linux Sound Architecture PCM on '%s'", dev);
if (pa_create_io_events(u->pcm_handle, c->mainloop, &u->io_events, &u->n_io_events, io_callback, u) < 0) {
pa_log(__FILE__": failed to obtain file descriptors\n");
goto fail;
}
u->frame_size = frame_size;
u->fragment_size = period_size;
pa_log_info(__FILE__": using %u fragments of size %u bytes.\n", periods, u->fragment_size);
u->silence.memblock = pa_memblock_new(u->silence.length = u->fragment_size, c->memblock_stat);
assert(u->silence.memblock);
pa_silence_memblock(u->silence.memblock, &ss);
u->silence.index = 0;
u->memchunk.memblock = NULL;
u->memchunk.index = u->memchunk.length = 0;
ret = 0;
finish:
if (ma)
pa_modargs_free(ma);
return ret;
fail:
if (u)
pa__done(c, m);
goto finish;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u;
assert(c && m);
if (!(u = m->userdata))
return;
if (u->sink) {
pa_sink_disconnect(u->sink);
pa_sink_unref(u->sink);
}
if (u->io_events)
pa_free_io_events(c->mainloop, u->io_events, u->n_io_events);
if (u->pcm_handle) {
snd_pcm_drop(u->pcm_handle);
snd_pcm_close(u->pcm_handle);
}
if (u->memchunk.memblock)
pa_memblock_unref(u->memchunk.memblock);
if (u->silence.memblock)
pa_memblock_unref(u->silence.memblock);
pa_xfree(u);
}

View file

@ -0,0 +1,278 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <assert.h>
#include <stdio.h>
#ifdef HAVE_SYS_POLL_H
#include <sys/poll.h>
#else
#include "poll.h"
#endif
#include <asoundlib.h>
#include <polypcore/core.h>
#include <polypcore/module.h>
#include <polypcore/memchunk.h>
#include <polypcore/sink.h>
#include <polypcore/modargs.h>
#include <polypcore/util.h>
#include <polypcore/sample-util.h>
#include <polypcore/alsa-util.h>
#include <polypcore/xmalloc.h>
#include <polypcore/log.h>
#include "module-alsa-source-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("ALSA Source")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("source_name=<name for the source> device=<ALSA device> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
struct userdata {
snd_pcm_t *pcm_handle;
pa_source *source;
pa_io_event **io_events;
unsigned n_io_events;
size_t frame_size, fragment_size;
pa_memchunk memchunk;
pa_module *module;
};
static const char* const valid_modargs[] = {
"device",
"source_name",
"channels",
"rate",
"format",
"fragments",
"fragment_size",
NULL
};
#define DEFAULT_SOURCE_NAME "alsa_input"
#define DEFAULT_DEVICE "hw:0,0"
static void update_usage(struct userdata *u) {
pa_module_set_used(u->module,
(u->source ? pa_idxset_size(u->source->outputs) : 0));
}
static void xrun_recovery(struct userdata *u) {
assert(u);
pa_log(__FILE__": *** ALSA-XRUN (capture) ***\n");
if (snd_pcm_prepare(u->pcm_handle) < 0)
pa_log(__FILE__": snd_pcm_prepare() failed\n");
}
static void do_read(struct userdata *u) {
assert(u);
update_usage(u);
for (;;) {
pa_memchunk post_memchunk;
snd_pcm_sframes_t frames;
size_t l;
if (!u->memchunk.memblock) {
u->memchunk.memblock = pa_memblock_new(u->memchunk.length = u->fragment_size, u->source->core->memblock_stat);
u->memchunk.index = 0;
}
assert(u->memchunk.memblock && u->memchunk.memblock->data && u->memchunk.length && u->memchunk.memblock->length && (u->memchunk.length % u->frame_size) == 0);
if ((frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length / u->frame_size)) < 0) {
if (frames == -EAGAIN)
return;
if (frames == -EPIPE) {
xrun_recovery(u);
continue;
}
pa_log(__FILE__": snd_pcm_readi() failed: %s\n", strerror(-frames));
return;
}
l = frames * u->frame_size;
post_memchunk = u->memchunk;
post_memchunk.length = l;
pa_source_post(u->source, &post_memchunk);
u->memchunk.index += l;
u->memchunk.length -= l;
if (u->memchunk.length == 0) {
pa_memblock_unref(u->memchunk.memblock);
u->memchunk.memblock = NULL;
u->memchunk.index = u->memchunk.length = 0;
}
break;
}
}
static void io_callback(pa_mainloop_api*a, pa_io_event *e, PA_GCC_UNUSED int fd, PA_GCC_UNUSED pa_io_event_flags_t f, void *userdata) {
struct userdata *u = userdata;
assert(u && a && e);
if (snd_pcm_state(u->pcm_handle) == SND_PCM_STATE_XRUN)
xrun_recovery(u);
do_read(u);
}
static pa_usec_t source_get_latency_cb(pa_source *s) {
struct userdata *u = s->userdata;
snd_pcm_sframes_t frames;
assert(s && u && u->source);
if (snd_pcm_delay(u->pcm_handle, &frames) < 0) {
pa_log(__FILE__": failed to get delay\n");
s->get_latency = NULL;
return 0;
}
return pa_bytes_to_usec(frames * u->frame_size, &s->sample_spec);
}
int pa__init(pa_core *c, pa_module*m) {
pa_modargs *ma = NULL;
int ret = -1;
struct userdata *u = NULL;
const char *dev;
pa_sample_spec ss;
unsigned periods, fragsize;
snd_pcm_uframes_t period_size;
size_t frame_size;
int err;
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments\n");
goto fail;
}
ss = c->default_sample_spec;
if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
pa_log(__FILE__": failed to parse sample specification\n");
goto fail;
}
frame_size = pa_frame_size(&ss);
periods = 12;
fragsize = 1024;
if (pa_modargs_get_value_u32(ma, "fragments", &periods) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &fragsize) < 0) {
pa_log(__FILE__": failed to parse buffer metrics\n");
goto fail;
}
period_size = fragsize;
u = pa_xmalloc0(sizeof(struct userdata));
m->userdata = u;
u->module = m;
snd_config_update_free_global();
if ((err = snd_pcm_open(&u->pcm_handle, dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) {
pa_log(__FILE__": Error opening PCM device %s: %s\n", dev, snd_strerror(err));
goto fail;
}
if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &periods, &period_size)) < 0) {
pa_log(__FILE__": Failed to set hardware parameters: %s\n", snd_strerror(err));
goto fail;
}
u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
assert(u->source);
u->source->userdata = u;
u->source->get_latency = source_get_latency_cb;
pa_source_set_owner(u->source, m);
u->source->description = pa_sprintf_malloc("Advanced Linux Sound Architecture PCM on '%s'", dev);
if (pa_create_io_events(u->pcm_handle, c->mainloop, &u->io_events, &u->n_io_events, io_callback, u) < 0) {
pa_log(__FILE__": failed to obtain file descriptors\n");
goto fail;
}
u->frame_size = frame_size;
u->fragment_size = period_size;
pa_log(__FILE__": using %u fragments of size %u bytes.\n", periods, u->fragment_size);
u->memchunk.memblock = NULL;
u->memchunk.index = u->memchunk.length = 0;
snd_pcm_start(u->pcm_handle);
ret = 0;
finish:
if (ma)
pa_modargs_free(ma);
return ret;
fail:
if (u)
pa__done(c, m);
goto finish;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u;
assert(c && m);
if (!(u = m->userdata))
return;
if (u->source) {
pa_source_disconnect(u->source);
pa_source_unref(u->source);
}
if (u->io_events)
pa_free_io_events(c->mainloop, u->io_events, u->n_io_events);
if (u->pcm_handle) {
snd_pcm_drop(u->pcm_handle);
snd_pcm_close(u->pcm_handle);
}
if (u->memchunk.memblock)
pa_memblock_unref(u->memchunk.memblock);
pa_xfree(u);
}

88
src/modules/module-cli.c Normal file
View file

@ -0,0 +1,88 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <polypcore/module.h>
#include <polypcore/iochannel.h>
#include <polypcore/cli.h>
#include <polypcore/sioman.h>
#include <polypcore/log.h>
#include "module-cli-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("Command line interface")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("No arguments")
static void eof_cb(pa_cli*c, void *userdata) {
pa_module *m = userdata;
assert(c && m);
pa_module_unload_request(m);
}
int pa__init(pa_core *c, pa_module*m) {
pa_iochannel *io;
assert(c && m);
if (c->running_as_daemon) {
pa_log_info(__FILE__": Running as daemon so won't load this module.\n");
return 0;
}
if (m->argument) {
pa_log(__FILE__": module doesn't accept arguments.\n");
return -1;
}
if (pa_stdio_acquire() < 0) {
pa_log(__FILE__": STDIN/STDUSE already in use.\n");
return -1;
}
io = pa_iochannel_new(c->mainloop, STDIN_FILENO, STDOUT_FILENO);
assert(io);
pa_iochannel_set_noclose(io, 1);
m->userdata = pa_cli_new(c, io, m);
assert(m->userdata);
pa_cli_set_eof_callback(m->userdata, eof_cb, m);
return 0;
}
void pa__done(pa_core *c, pa_module*m) {
assert(c && m);
if (c->running_as_daemon == 0) {
pa_cli_free(m->userdata);
pa_stdio_release();
}
}

View file

@ -0,0 +1,394 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <assert.h>
#include <stdio.h>
#include <polypcore/module.h>
#include <polypcore/llist.h>
#include <polypcore/sink.h>
#include <polypcore/sink-input.h>
#include <polypcore/memblockq.h>
#include <polypcore/log.h>
#include <polypcore/util.h>
#include <polypcore/xmalloc.h>
#include <polypcore/modargs.h>
#include <polypcore/namereg.h>
#include "module-combine-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("Combine multiple sinks to one")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("sink_name=<name for the sink> master=<master sink> slaves=<slave sinks> adjust_time=<seconds> resample_method=<method>")
#define DEFAULT_SINK_NAME "combined"
#define MEMBLOCKQ_MAXLENGTH (1024*170)
#define RENDER_SIZE (1024*10)
#define DEFAULT_ADJUST_TIME 20
static const char* const valid_modargs[] = {
"sink_name",
"master",
"slaves",
"adjust_time",
"resample_method",
NULL
};
struct output {
struct userdata *userdata;
pa_sink_input *sink_input;
size_t counter;
pa_memblockq *memblockq;
pa_usec_t total_latency;
PA_LLIST_FIELDS(struct output);
};
struct userdata {
pa_module *module;
pa_core *core;
pa_sink *sink;
unsigned n_outputs;
struct output *master;
pa_time_event *time_event;
uint32_t adjust_time;
PA_LLIST_HEAD(struct output, outputs);
};
static void output_free(struct output *o);
static void clear_up(struct userdata *u);
static void update_usage(struct userdata *u) {
pa_module_set_used(u->module,
(u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
(u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0));
}
static void adjust_rates(struct userdata *u) {
struct output *o;
pa_usec_t max_sink_latency = 0, min_total_latency = (pa_usec_t) -1, target_latency;
uint32_t base_rate;
assert(u && u->sink);
for (o = u->outputs; o; o = o->next) {
uint32_t sink_latency = o->sink_input->sink ? pa_sink_get_latency(o->sink_input->sink) : 0;
o->total_latency = sink_latency + pa_sink_input_get_latency(o->sink_input);
if (sink_latency > max_sink_latency)
max_sink_latency = sink_latency;
if (o->total_latency < min_total_latency)
min_total_latency = o->total_latency;
}
assert(min_total_latency != (pa_usec_t) -1);
target_latency = max_sink_latency > min_total_latency ? max_sink_latency : min_total_latency;
pa_log_info(__FILE__": [%s] target latency is %0.0f usec.\n", u->sink->name, (float) target_latency);
base_rate = u->sink->sample_spec.rate;
for (o = u->outputs; o; o = o->next) {
uint32_t r = base_rate;
if (o->total_latency < target_latency)
r -= (uint32_t) (((((double) target_latency - o->total_latency))/u->adjust_time)*r/ 1000000);
else if (o->total_latency > target_latency)
r += (uint32_t) (((((double) o->total_latency - target_latency))/u->adjust_time)*r/ 1000000);
if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1))
pa_log_warn(__FILE__": [%s] sample rates too different, not adjusting (%u vs. %u).\n", o->sink_input->name, base_rate, r);
else {
pa_log_info(__FILE__": [%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.\n", o->sink_input->name, r, (double) r / base_rate, (float) o->total_latency);
pa_sink_input_set_rate(o->sink_input, r);
}
}
}
static void request_memblock(struct userdata *u) {
pa_memchunk chunk;
struct output *o;
assert(u && u->sink);
update_usage(u);
if (pa_sink_render(u->sink, RENDER_SIZE, &chunk) < 0)
return;
for (o = u->outputs; o; o = o->next)
pa_memblockq_push_align(o->memblockq, &chunk, 0);
pa_memblock_unref(chunk.memblock);
}
static void time_callback(pa_mainloop_api*a, pa_time_event* e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
struct userdata *u = userdata;
struct timeval n;
assert(u && a && u->time_event == e);
adjust_rates(u);
pa_gettimeofday(&n);
n.tv_sec += u->adjust_time;
u->sink->core->mainloop->time_restart(e, &n);
}
static int sink_input_peek_cb(pa_sink_input *i, pa_memchunk *chunk) {
struct output *o = i->userdata;
assert(i && o && o->sink_input && chunk);
if (pa_memblockq_peek(o->memblockq, chunk) >= 0)
return 0;
/* Try harder */
request_memblock(o->userdata);
return pa_memblockq_peek(o->memblockq, chunk);
}
static void sink_input_drop_cb(pa_sink_input *i, const pa_memchunk *chunk, size_t length) {
struct output *o = i->userdata;
assert(i && o && o->sink_input && chunk && length);
pa_memblockq_drop(o->memblockq, chunk, length);
o->counter += length;
}
static void sink_input_kill_cb(pa_sink_input *i) {
struct output *o = i->userdata;
assert(i && o && o->sink_input);
pa_module_unload_request(o->userdata->module);
clear_up(o->userdata);
}
static pa_usec_t sink_input_get_latency_cb(pa_sink_input *i) {
struct output *o = i->userdata;
assert(i && o && o->sink_input);
return pa_bytes_to_usec(pa_memblockq_get_length(o->memblockq), &i->sample_spec);
}
static pa_usec_t sink_get_latency_cb(pa_sink *s) {
struct userdata *u = s->userdata;
assert(s && u && u->sink && u->master);
return pa_sink_input_get_latency(u->master->sink_input);
}
static struct output *output_new(struct userdata *u, pa_sink *sink, int resample_method) {
struct output *o = NULL;
char t[256];
assert(u && sink && u->sink);
o = pa_xmalloc(sizeof(struct output));
o->userdata = u;
o->counter = 0;
o->memblockq = pa_memblockq_new(MEMBLOCKQ_MAXLENGTH, MEMBLOCKQ_MAXLENGTH, pa_frame_size(&u->sink->sample_spec), 0, 0, sink->core->memblock_stat);
snprintf(t, sizeof(t), "%s: output #%u", u->sink->name, u->n_outputs+1);
if (!(o->sink_input = pa_sink_input_new(sink, __FILE__, t, &u->sink->sample_spec, &u->sink->channel_map, 1, resample_method)))
goto fail;
o->sink_input->get_latency = sink_input_get_latency_cb;
o->sink_input->peek = sink_input_peek_cb;
o->sink_input->drop = sink_input_drop_cb;
o->sink_input->kill = sink_input_kill_cb;
o->sink_input->userdata = o;
o->sink_input->owner = u->module;
PA_LLIST_PREPEND(struct output, u->outputs, o);
u->n_outputs++;
return o;
fail:
if (o) {
if (o->sink_input) {
pa_sink_input_disconnect(o->sink_input);
pa_sink_input_unref(o->sink_input);
}
if (o->memblockq)
pa_memblockq_free(o->memblockq);
pa_xfree(o);
}
return NULL;
}
static void output_free(struct output *o) {
assert(o);
PA_LLIST_REMOVE(struct output, o->userdata->outputs, o);
o->userdata->n_outputs--;
pa_memblockq_free(o->memblockq);
pa_sink_input_disconnect(o->sink_input);
pa_sink_input_unref(o->sink_input);
pa_xfree(o);
}
static void clear_up(struct userdata *u) {
struct output *o;
assert(u);
if (u->time_event) {
u->core->mainloop->time_free(u->time_event);
u->time_event = NULL;
}
while ((o = u->outputs))
output_free(o);
u->master = NULL;
if (u->sink) {
pa_sink_disconnect(u->sink);
pa_sink_unref(u->sink);
u->sink = NULL;
}
}
int pa__init(pa_core *c, pa_module*m) {
struct userdata *u;
pa_modargs *ma = NULL;
const char *master_name, *slaves, *rm;
pa_sink *master_sink;
char *n = NULL;
const char*split_state;
struct timeval tv;
int resample_method = -1;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments\n");
goto fail;
}
if ((rm = pa_modargs_get_value(ma, "resample_method", NULL))) {
if ((resample_method = pa_parse_resample_method(rm)) < 0) {
pa_log(__FILE__": invalid resample method '%s'\n", rm);
goto fail;
}
}
u = pa_xmalloc(sizeof(struct userdata));
m->userdata = u;
u->sink = NULL;
u->n_outputs = 0;
u->master = NULL;
u->module = m;
u->core = c;
u->time_event = NULL;
u->adjust_time = DEFAULT_ADJUST_TIME;
PA_LLIST_HEAD_INIT(struct output, u->outputs);
if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) {
pa_log(__FILE__": failed to parse adjust_time value\n");
goto fail;
}
if (!(master_name = pa_modargs_get_value(ma, "master", NULL)) || !(slaves = pa_modargs_get_value(ma, "slaves", NULL))) {
pa_log(__FILE__": no master or slave sinks specified\n");
goto fail;
}
if (!(master_sink = pa_namereg_get(c, master_name, PA_NAMEREG_SINK, 1))) {
pa_log(__FILE__": invalid master sink '%s'\n", master_name);
goto fail;
}
if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &master_sink->sample_spec, &master_sink->channel_map))) {
pa_log(__FILE__": failed to create sink\n");
goto fail;
}
pa_sink_set_owner(u->sink, m);
u->sink->description = pa_sprintf_malloc("Combined sink");
u->sink->get_latency = sink_get_latency_cb;
u->sink->userdata = u;
if (!(u->master = output_new(u, master_sink, resample_method))) {
pa_log(__FILE__": failed to create master sink input on sink '%s'.\n", u->sink->name);
goto fail;
}
split_state = NULL;
while ((n = pa_split(slaves, ",", &split_state))) {
pa_sink *slave_sink;
if (!(slave_sink = pa_namereg_get(c, n, PA_NAMEREG_SINK, 1))) {
pa_log(__FILE__": invalid slave sink '%s'\n", n);
goto fail;
}
pa_xfree(n);
if (!output_new(u, slave_sink, resample_method)) {
pa_log(__FILE__": failed to create slave sink input on sink '%s'.\n", slave_sink->name);
goto fail;
}
}
if (u->n_outputs <= 1)
pa_log_warn(__FILE__": WARNING: no slave sinks specified.\n");
if (u->adjust_time > 0) {
pa_gettimeofday(&tv);
tv.tv_sec += u->adjust_time;
u->time_event = c->mainloop->time_new(c->mainloop, &tv, time_callback, u);
}
pa_modargs_free(ma);
return 0;
fail:
pa_xfree(n);
if (ma)
pa_modargs_free(ma);
pa__done(c, m);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u;
assert(c && m);
if (!(u = m->userdata))
return;
clear_up(u);
pa_xfree(u);
}

View file

@ -0,0 +1,29 @@
dnl $Id$
changecom(`/*', `*/')dnl
define(`module_name', patsubst(patsubst(patsubst(fname, `-symdef.h$'), `^.*/'), `[^0-9a-zA-Z]', `_'))dnl
define(`c_symbol', patsubst(module_name, `[^0-9a-zA-Z]', `_'))dnl
define(`c_macro', patsubst(module_name, `[^0-9a-zA-Z]', `'))dnl
define(`incmacro', `foo'c_macro`symdeffoo')dnl
define(`gen_symbol', `#define $1 'module_name`_LTX_$1')dnl
#ifndef incmacro
#define incmacro
#include <polypcore/core.h>
#include <polypcore/module.h>
gen_symbol(pa__init)
gen_symbol(pa__done)
gen_symbol(pa__get_author)
gen_symbol(pa__get_description)
gen_symbol(pa__get_usage)
gen_symbol(pa__get_version)
int pa__init(struct pa_core *c, struct pa_module*m);
void pa__done(struct pa_core *c, struct pa_module*m);
const char* pa__get_author(void);
const char* pa__get_description(void);
const char* pa__get_usage(void);
const char* pa__get_version(void);
#endif

227
src/modules/module-detect.c Normal file
View file

@ -0,0 +1,227 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdlib.h>
#include <polypcore/module.h>
#include <polypcore/modargs.h>
#include <polypcore/xmalloc.h>
#include <polypcore/log.h>
#include "module-detect-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("just-one=<boolean>")
static const char *endswith(const char *haystack, const char *needle) {
size_t l, m;
const char *p;
if ((l = strlen(haystack)) < (m = strlen(needle)))
return NULL;
if (strcmp(p = haystack + l - m, needle))
return NULL;
return p;
}
#ifdef HAVE_ALSA
static int detect_alsa(pa_core *c, int just_one) {
FILE *f;
int n = 0, n_sink = 0, n_source = 0;
if (!(f = fopen("/proc/asound/devices", "r"))) {
if (errno != ENOENT)
pa_log_error(__FILE__": open(\"/proc/asound/devices\") failed: %s\n", strerror(errno));
return -1;
}
while (!feof(f)) {
char line[64], args[64];
unsigned device, subdevice;
int is_sink;
if (!fgets(line, sizeof(line), f))
break;
line[strcspn(line, "\r\n")] = 0;
if (endswith(line, "digital audio playback"))
is_sink = 1;
else if (endswith(line, "digital audio capture"))
is_sink = 0;
else
continue;
if (just_one && is_sink && n_sink >= 1)
continue;
if (just_one && !is_sink && n_source >= 1)
continue;
if (sscanf(line, " %*i: [%u- %u]: ", &device, &subdevice) != 2)
continue;
/* Only one sink per device */
if (subdevice != 0)
continue;
snprintf(args, sizeof(args), "device=hw:%u,0", device);
if (!pa_module_load(c, is_sink ? "module-alsa-sink" : "module-alsa-source", args))
continue;
n++;
if (is_sink)
n_sink++;
else
n_source++;
}
fclose(f);
return n;
}
#endif
#ifdef HAVE_OSS
static int detect_oss(pa_core *c, int just_one) {
FILE *f;
int n = 0, b = 0;
if (!(f = fopen("/dev/sndstat", "r")) &&
!(f = fopen("/proc/sndstat", "r")) &&
!(f = fopen("/proc/asound/oss/sndstat", "r"))) {
if (errno != ENOENT)
pa_log_error(__FILE__": failed to open OSS sndstat device: %s\n", strerror(errno));
return -1;
}
while (!feof(f)) {
char line[64], args[64];
unsigned device;
if (!fgets(line, sizeof(line), f))
break;
line[strcspn(line, "\r\n")] = 0;
if (!b) {
b = strcmp(line, "Audio devices:") == 0;
continue;
}
if (line[0] == 0)
break;
if (sscanf(line, "%u: ", &device) != 1)
continue;
if (device == 0)
snprintf(args, sizeof(args), "device=/dev/dsp");
else
snprintf(args, sizeof(args), "device=/dev/dsp%u", device);
if (!pa_module_load(c, "module-oss", args))
continue;
n++;
if (just_one)
break;
}
fclose(f);
return n;
}
#endif
int pa__init(pa_core *c, pa_module*m) {
int just_one = 0, n = 0;
pa_modargs *ma;
static const char* const valid_modargs[] = {
"just-one",
NULL
};
assert(c);
assert(m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": Failed to parse module arguments\n");
goto fail;
}
if (pa_modargs_get_value_boolean(ma, "just-one", &just_one) < 0) {
pa_log(__FILE__": just_one= expects a boolean argument.\n");
goto fail;
}
#if HAVE_ALSA
if ((n = detect_alsa(c, just_one)) <= 0)
#endif
#if HAVE_OSS
if ((n = detect_oss(c, just_one)) <= 0)
#endif
{
pa_log_warn(__FILE__": failed to detect any sound hardware.\n");
goto fail;
}
pa_log_info(__FILE__": loaded %i modules.\n", n);
/* We were successful and can unload ourselves now. */
pa_module_unload_request(m);
pa_modargs_free(ma);
return 0;
fail:
if (ma)
pa_modargs_free(ma);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
/* NOP */
}

View file

@ -0,0 +1,81 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <polypcore/module.h>
#include <polypcore/modargs.h>
#include <polypcore/util.h>
#include <polypcore/log.h>
#include "module-esound-compat-spawnfd-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnfd emulation")
PA_MODULE_USAGE("fd=<file descriptor>")
PA_MODULE_VERSION(PACKAGE_VERSION)
static const char* const valid_modargs[] = {
"fd",
NULL,
};
int pa__init(pa_core *c, pa_module*m) {
pa_modargs *ma = NULL;
int ret = -1, fd = -1;
char x = 1;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs)) ||
pa_modargs_get_value_s32(ma, "fd", &fd) < 0 ||
fd < 0) {
pa_log(__FILE__": Failed to parse module arguments\n");
goto finish;
}
if (pa_loop_write(fd, &x, sizeof(x)) != sizeof(x))
pa_log(__FILE__": WARNING: write(%u, 1, 1) failed: %s\n", fd, strerror(errno));
close(fd);
pa_module_unload_request(m);
ret = 0;
finish:
if (ma)
pa_modargs_free(ma);
return ret;
}
void pa__done(pa_core *c, pa_module*m) {
assert(c && m);
}

View file

@ -0,0 +1,79 @@
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <polypcore/module.h>
#include <polypcore/util.h>
#include <polypcore/modargs.h>
#include <polypcore/log.h>
#include "module-esound-compat-spawnpid-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnpid emulation")
PA_MODULE_USAGE("pid=<process id>")
PA_MODULE_VERSION(PACKAGE_VERSION)
static const char* const valid_modargs[] = {
"pid",
NULL,
};
int pa__init(pa_core *c, pa_module*m) {
pa_modargs *ma = NULL;
int ret = -1;
uint32_t pid = 0;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs)) ||
pa_modargs_get_value_u32(ma, "pid", &pid) < 0 ||
!pid) {
pa_log(__FILE__": Failed to parse module arguments\n");
goto finish;
}
if (kill(pid, SIGUSR1) < 0)
pa_log(__FILE__": WARNING: kill(%u) failed: %s\n", pid, strerror(errno));
pa_module_unload_request(m);
ret = 0;
finish:
if (ma)
pa_modargs_free(ma);
return ret;
}
void pa__done(pa_core *c, pa_module*m) {
assert(c && m);
}

View file

@ -0,0 +1,427 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <sys/stat.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <polypcore/iochannel.h>
#include <polypcore/sink.h>
#include <polypcore/module.h>
#include <polypcore/util.h>
#include <polypcore/modargs.h>
#include <polypcore/xmalloc.h>
#include <polypcore/log.h>
#include <polypcore/socket-client.h>
#include <polypcore/esound.h>
#include <polypcore/authkey.h>
#include "module-esound-sink-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("ESOUND Sink")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("sink_name=<name for the sink> server=<address> cookie=<filename> format=<sample format> channels=<number of channels> rate=<sample rate>")
#define DEFAULT_SINK_NAME "esound_output"
struct userdata {
pa_core *core;
pa_sink *sink;
pa_iochannel *io;
pa_socket_client *client;
pa_defer_event *defer_event;
pa_memchunk memchunk;
pa_module *module;
void *write_data;
size_t write_length, write_index;
void *read_data;
size_t read_length, read_index;
enum { STATE_AUTH, STATE_LATENCY, STATE_RUNNING, STATE_DEAD } state;
pa_usec_t latency;
esd_format_t format;
int32_t rate;
};
static const char* const valid_modargs[] = {
"server",
"cookie",
"rate",
"format",
"channels",
"sink_name",
NULL
};
static void cancel(struct userdata *u) {
assert(u);
u->state = STATE_DEAD;
if (u->io) {
pa_iochannel_free(u->io);
u->io = NULL;
}
if (u->defer_event) {
u->core->mainloop->defer_free(u->defer_event);
u->defer_event = NULL;
}
if (u->sink) {
pa_sink_disconnect(u->sink);
pa_sink_unref(u->sink);
u->sink = NULL;
}
if (u->module) {
pa_module_unload_request(u->module);
u->module = NULL;
}
}
static int do_write(struct userdata *u) {
ssize_t r;
assert(u);
if (!pa_iochannel_is_writable(u->io))
return 0;
if (u->write_data) {
assert(u->write_index < u->write_length);
if ((r = pa_iochannel_write(u->io, (uint8_t*) u->write_data + u->write_index, u->write_length - u->write_index)) <= 0) {
pa_log(__FILE__": write() failed: %s\n", strerror(errno));
return -1;
}
u->write_index += r;
assert(u->write_index <= u->write_length);
if (u->write_index == u->write_length) {
free(u->write_data);
u->write_data = NULL;
u->write_index = u->write_length = 0;
}
} else if (u->state == STATE_RUNNING) {
pa_module_set_used(u->module, pa_idxset_size(u->sink->inputs) + pa_idxset_size(u->sink->monitor_source->outputs));
if (!u->memchunk.length)
if (pa_sink_render(u->sink, 8192, &u->memchunk) < 0)
return 0;
assert(u->memchunk.memblock && u->memchunk.length);
if ((r = pa_iochannel_write(u->io, (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length)) < 0) {
pa_log(__FILE__": write() failed: %s\n", strerror(errno));
return -1;
}
u->memchunk.index += r;
u->memchunk.length -= r;
if (u->memchunk.length <= 0) {
pa_memblock_unref(u->memchunk.memblock);
u->memchunk.memblock = NULL;
}
}
return 0;
}
static int handle_response(struct userdata *u) {
assert(u);
switch (u->state) {
case STATE_AUTH:
assert(u->read_length == sizeof(int32_t));
/* Process auth data */
if (!*(int32_t*) u->read_data) {
pa_log(__FILE__": Authentication failed: %s\n", strerror(errno));
return -1;
}
/* Request latency data */
assert(!u->write_data);
*(int32_t*) (u->write_data = pa_xmalloc(u->write_length = sizeof(int32_t))) = ESD_PROTO_LATENCY;
u->write_index = 0;
u->state = STATE_LATENCY;
/* Space for next response */
assert(u->read_length >= sizeof(int32_t));
u->read_index = 0;
u->read_length = sizeof(int32_t);
break;
case STATE_LATENCY: {
int32_t *p;
assert(u->read_length == sizeof(int32_t));
/* Process latency info */
u->latency = (pa_usec_t) ((double) (*(int32_t*) u->read_data) * 1000000 / 44100);
if (u->latency > 10000000) {
pa_log(__FILE__": WARNING! Invalid latency information received from server\n");
u->latency = 0;
}
/* Create stream */
assert(!u->write_data);
p = u->write_data = pa_xmalloc0(u->write_length = sizeof(int32_t)*3+ESD_NAME_MAX);
*(p++) = ESD_PROTO_STREAM_PLAY;
*(p++) = u->format;
*(p++) = u->rate;
pa_strlcpy((char*) p, "Polypaudio Tunnel", ESD_NAME_MAX);
u->write_index = 0;
u->state = STATE_RUNNING;
/* Don't read any further */
pa_xfree(u->read_data);
u->read_data = NULL;
u->read_index = u->read_length = 0;
break;
}
default:
abort();
}
return 0;
}
static int do_read(struct userdata *u) {
assert(u);
if (!pa_iochannel_is_readable(u->io))
return 0;
if (u->state == STATE_AUTH || u->state == STATE_LATENCY) {
ssize_t r;
if (!u->read_data)
return 0;
assert(u->read_index < u->read_length);
if ((r = pa_iochannel_read(u->io, (uint8_t*) u->read_data + u->read_index, u->read_length - u->read_index)) <= 0) {
pa_log(__FILE__": read() failed: %s\n", r < 0 ? strerror(errno) : "EOF");
cancel(u);
return -1;
}
u->read_index += r;
assert(u->read_index <= u->read_length);
if (u->read_index == u->read_length)
return handle_response(u);
}
return 0;
}
static void do_work(struct userdata *u) {
assert(u);
u->core->mainloop->defer_enable(u->defer_event, 0);
if (do_read(u) < 0 || do_write(u) < 0)
cancel(u);
}
static void notify_cb(pa_sink*s) {
struct userdata *u = s->userdata;
assert(s && u);
if (pa_iochannel_is_writable(u->io))
u->core->mainloop->defer_enable(u->defer_event, 1);
}
static pa_usec_t get_latency_cb(pa_sink *s) {
struct userdata *u = s->userdata;
assert(s && u);
return
u->latency +
(u->memchunk.memblock ? pa_bytes_to_usec(u->memchunk.length, &s->sample_spec) : 0);
}
static void defer_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC_UNUSED pa_defer_event*e, void *userdata) {
struct userdata *u = userdata;
assert(u);
do_work(u);
}
static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) {
struct userdata *u = userdata;
assert(u);
do_work(u);
}
static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, void *userdata) {
struct userdata *u = userdata;
pa_socket_client_unref(u->client);
u->client = NULL;
if (!io) {
pa_log(__FILE__": connection failed: %s\n", strerror(errno));
cancel(u);
return;
}
u->io = io;
pa_iochannel_set_callback(u->io, io_callback, u);
}
int pa__init(pa_core *c, pa_module*m) {
struct userdata *u = NULL;
const char *p;
pa_sample_spec ss;
pa_modargs *ma = NULL;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments\n");
goto fail;
}
ss = c->default_sample_spec;
if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
pa_log(__FILE__": invalid sample format specification\n");
goto fail;
}
if ((ss.format != PA_SAMPLE_U8 && ss.format != PA_SAMPLE_S16NE) ||
(ss.channels > 2)) {
pa_log(__FILE__": esound sample type support is limited to mono/stereo and U8 or S16NE sample data\n");
goto fail;
}
u = pa_xmalloc0(sizeof(struct userdata));
u->core = c;
u->module = m;
m->userdata = u;
u->format =
(ss.format == PA_SAMPLE_U8 ? ESD_BITS8 : ESD_BITS16) |
(ss.channels == 2 ? ESD_STEREO : ESD_MONO);
u->rate = ss.rate;
u->sink = NULL;
u->client = NULL;
u->io = NULL;
u->read_data = u->write_data = NULL;
u->read_index = u->write_index = u->read_length = u->write_length = 0;
u->state = STATE_AUTH;
u->latency = 0;
if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) {
pa_log(__FILE__": failed to create sink.\n");
goto fail;
}
if (!(u->client = pa_socket_client_new_string(u->core->mainloop, p = pa_modargs_get_value(ma, "server", ESD_UNIX_SOCKET_NAME), ESD_DEFAULT_PORT))) {
pa_log(__FILE__": failed to connect to server.\n");
goto fail;
}
pa_socket_client_set_callback(u->client, on_connection, u);
/* Prepare the initial request */
u->write_data = pa_xmalloc(u->write_length = ESD_KEY_LEN + sizeof(int32_t));
if (pa_authkey_load_auto(pa_modargs_get_value(ma, "cookie", ".esd_auth"), u->write_data, ESD_KEY_LEN) < 0) {
pa_log(__FILE__": failed to load cookie\n");
goto fail;
}
*(int32_t*) ((uint8_t*) u->write_data + ESD_KEY_LEN) = ESD_ENDIAN_KEY;
/* Reserve space for the response */
u->read_data = pa_xmalloc(u->read_length = sizeof(int32_t));
u->sink->notify = notify_cb;
u->sink->get_latency = get_latency_cb;
u->sink->userdata = u;
pa_sink_set_owner(u->sink, m);
u->sink->description = pa_sprintf_malloc("Esound sink '%s'", p);
u->memchunk.memblock = NULL;
u->memchunk.length = 0;
u->defer_event = c->mainloop->defer_new(c->mainloop, defer_callback, u);
c->mainloop->defer_enable(u->defer_event, 0);
pa_modargs_free(ma);
return 0;
fail:
if (ma)
pa_modargs_free(ma);
pa__done(c, m);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u;
assert(c && m);
if (!(u = m->userdata))
return;
u->module = NULL;
cancel(u);
if (u->memchunk.memblock)
pa_memblock_unref(u->memchunk.memblock);
if (u->client)
pa_socket_client_unref(u->client);
pa_xfree(u->read_data);
pa_xfree(u->write_data);
pa_xfree(u);
}

241
src/modules/module-lirc.c Normal file
View file

@ -0,0 +1,241 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <lirc/lirc_client.h>
#include <stdlib.h>
#include <polypcore/module.h>
#include <polypcore/log.h>
#include <polypcore/namereg.h>
#include <polypcore/sink.h>
#include <polypcore/xmalloc.h>
#include <polypcore/modargs.h>
#include "module-lirc-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("LIRC volume control")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("config=<config file> sink=<sink name> appname=<lirc application name>")
static const char* const valid_modargs[] = {
"config",
"sink",
"appname",
NULL,
};
struct userdata {
int lirc_fd;
pa_io_event *io;
struct lirc_config *config;
char *sink_name;
pa_module *module;
float mute_toggle_save;
};
static int lirc_in_use = 0;
static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) {
struct userdata *u = userdata;
char *name = NULL, *code = NULL;
assert(io);
assert(u);
if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
pa_log(__FILE__": lost connection to LIRC daemon.\n");
goto fail;
}
if (events & PA_IO_EVENT_INPUT) {
char *c;
if (lirc_nextcode(&code) != 0 || !code) {
pa_log(__FILE__": lirc_nextcode() failed.\n");
goto fail;
}
c = pa_xstrdup(code);
c[strcspn(c, "\n\r")] = 0;
pa_log_debug(__FILE__": raw IR code '%s'\n", c);
pa_xfree(c);
while (lirc_code2char(u->config, code, &name) == 0 && name) {
enum { INVALID, UP, DOWN, MUTE, RESET, MUTE_TOGGLE } volchange = INVALID;
pa_log_info(__FILE__": translated IR code '%s'\n", name);
if (strcasecmp(name, "volume-up") == 0)
volchange = UP;
else if (strcasecmp(name, "volume-down") == 0)
volchange = DOWN;
else if (strcasecmp(name, "mute") == 0)
volchange = MUTE;
else if (strcasecmp(name, "mute-toggle") == 0)
volchange = MUTE_TOGGLE;
else if (strcasecmp(name, "reset") == 0)
volchange = RESET;
if (volchange == INVALID)
pa_log_warn(__FILE__": recieved unknown IR code '%s'\n", name);
else {
pa_sink *s;
if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1)))
pa_log(__FILE__": failed to get sink '%s'\n", u->sink_name);
else {
pa_volume_t v = pa_cvolume_avg(pa_sink_get_volume(s, PA_MIXER_HARDWARE));
pa_cvolume cv;
#define DELTA (PA_VOLUME_NORM/20)
switch (volchange) {
case UP:
v += PA_VOLUME_NORM/20;
break;
case DOWN:
if (v > DELTA)
v -= DELTA;
else
v = PA_VOLUME_MUTED;
break;
case MUTE:
v = PA_VOLUME_MUTED;
break;
case RESET:
v = PA_VOLUME_NORM;
break;
case MUTE_TOGGLE: {
if (v > 0) {
u->mute_toggle_save = v;
v = PA_VOLUME_MUTED;
} else
v = u->mute_toggle_save;
}
default:
;
}
pa_cvolume_set(&cv, PA_CHANNELS_MAX, v);
pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv);
}
}
}
}
free(code);
return;
fail:
u->module->core->mainloop->io_free(u->io);
u->io = NULL;
pa_module_unload_request(u->module);
free(code);
}
int pa__init(pa_core *c, pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u;
assert(c && m);
if (lirc_in_use) {
pa_log(__FILE__": module-lirc may no be loaded twice.\n");
return -1;
}
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": Failed to parse module arguments\n");
goto fail;
}
m->userdata = u = pa_xmalloc(sizeof(struct userdata));
u->module = m;
u->io = NULL;
u->config = NULL;
u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
u->lirc_fd = -1;
u->mute_toggle_save = 0;
if ((u->lirc_fd = lirc_init((char*) pa_modargs_get_value(ma, "appname", "polypaudio"), 1)) < 0) {
pa_log(__FILE__": lirc_init() failed.\n");
goto fail;
}
if (lirc_readconfig((char*) pa_modargs_get_value(ma, "config", NULL), &u->config, NULL) < 0) {
pa_log(__FILE__": lirc_readconfig() failed.\n");
goto fail;
}
u->io = c->mainloop->io_new(c->mainloop, u->lirc_fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u);
lirc_in_use = 1;
pa_modargs_free(ma);
return 0;
fail:
if (ma)
pa_modargs_free(ma);
pa__done(c, m);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u;
assert(c);
assert(m);
if (!(u = m->userdata))
return;
if (u->io)
m->core->mainloop->io_free(u->io);
if (u->config)
lirc_freeconfig(u->config);
if (u->lirc_fd >= 0)
lirc_deinit();
pa_xfree(u->sink_name);
pa_xfree(u);
lirc_in_use = 0;
}

235
src/modules/module-match.c Normal file
View file

@ -0,0 +1,235 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <polypcore/module.h>
#include <polypcore/util.h>
#include <polypcore/modargs.h>
#include <polypcore/log.h>
#include <polypcore/subscribe.h>
#include <polypcore/xmalloc.h>
#include <polypcore/sink-input.h>
#include "module-match-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("Sink input matching module")
PA_MODULE_USAGE("table=<filename>")
PA_MODULE_VERSION(PACKAGE_VERSION)
#define WHITESPACE "\n\r \t"
#ifndef DEFAULT_CONFIG_DIR
#define DEFAULT_CONFIG_DIR "/etc/polypaudio"
#endif
#define DEFAULT_MATCH_TABLE_FILE DEFAULT_CONFIG_DIR"/match.table"
#define DEFAULT_MATCH_TABLE_FILE_USER ".polypaudio/match.table"
static const char* const valid_modargs[] = {
"table",
NULL,
};
struct rule {
regex_t regex;
pa_volume_t volume;
struct rule *next;
};
struct userdata {
struct rule *rules;
pa_subscription *subscription;
};
static int load_rules(struct userdata *u, const char *filename) {
FILE *f;
int n = 0;
int ret = -1;
struct rule *end = NULL;
char *fn = NULL;
f = filename ?
fopen(fn = pa_xstrdup(filename), "r") :
pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn);
if (!f) {
pa_log(__FILE__": failed to open file '%s': %s\n", fn, strerror(errno));
goto finish;
}
while (!feof(f)) {
char *d, *v;
pa_volume_t volume;
uint32_t k;
regex_t regex;
char ln[256];
struct rule *rule;
if (!fgets(ln, sizeof(ln), f))
break;
n++;
pa_strip_nl(ln);
if (ln[0] == '#' || !*ln )
continue;
d = ln+strcspn(ln, WHITESPACE);
v = d+strspn(d, WHITESPACE);
if (!*v) {
pa_log(__FILE__ ": [%s:%u] failed to parse line - too few words\n", filename, n);
goto finish;
}
*d = 0;
if (pa_atou(v, &k) < 0) {
pa_log(__FILE__": [%s:%u] failed to parse volume\n", filename, n);
goto finish;
}
volume = (pa_volume_t) k;
if (regcomp(&regex, ln, REG_EXTENDED|REG_NOSUB) != 0) {
pa_log(__FILE__": [%s:%u] invalid regular expression\n", filename, n);
goto finish;
}
rule = pa_xmalloc(sizeof(struct rule));
rule->regex = regex;
rule->volume = volume;
rule->next = NULL;
if (end)
end->next = rule;
else
u->rules = rule;
end = rule;
*d = 0;
}
ret = 0;
finish:
if (f)
fclose(f);
if (fn)
pa_xfree(fn);
return ret;
}
static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
struct userdata *u = userdata;
pa_sink_input *si;
struct rule *r;
assert(c && u);
if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW))
return;
if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx)))
return;
if (!si->name)
return;
for (r = u->rules; r; r = r->next) {
if (!regexec(&r->regex, si->name, 0, NULL, 0)) {
pa_cvolume cv;
pa_log_debug(__FILE__": changing volume of sink input '%s' to 0x%03x\n", si->name, r->volume);
pa_cvolume_set(&cv, r->volume, si->sample_spec.channels);
pa_sink_input_set_volume(si, &cv);
}
}
}
int pa__init(pa_core *c, pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": Failed to parse module arguments\n");
goto fail;
}
u = pa_xmalloc(sizeof(struct userdata));
u->rules = NULL;
u->subscription = NULL;
m->userdata = u;
if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0)
goto fail;
u->subscription = pa_subscription_new(c, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u);
pa_modargs_free(ma);
return 0;
fail:
pa__done(c, m);
if (ma)
pa_modargs_free(ma);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata* u;
struct rule *r, *n;
assert(c && m);
if (!(u = m->userdata))
return;
if (u->subscription)
pa_subscription_free(u->subscription);
for (r = u->rules; r; r = n) {
n = r->next;
regfree(&r->regex);
pa_xfree(r);
}
pa_xfree(u);
}

View file

@ -0,0 +1,250 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <polypcore/module.h>
#include <polypcore/log.h>
#include <polypcore/namereg.h>
#include <polypcore/sink.h>
#include <polypcore/xmalloc.h>
#include <polypcore/modargs.h>
#include <polypcore/util.h>
#include "module-mmkbd-evdev-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("Multimedia keyboard support via Linux evdev")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("device=<evdev device> sink=<sink name>")
#define DEFAULT_DEVICE "/dev/input/event0"
/*
* This isn't defined in older kernel headers and there is no way of
* detecting it.
*/
struct _input_id {
__u16 bustype;
__u16 vendor;
__u16 product;
__u16 version;
};
static const char* const valid_modargs[] = {
"device",
"sink",
NULL,
};
struct userdata {
int fd;
pa_io_event *io;
char *sink_name;
pa_module *module;
float mute_toggle_save;
};
static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) {
struct userdata *u = userdata;
assert(io);
assert(u);
if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
pa_log(__FILE__": lost connection to evdev device.\n");
goto fail;
}
if (events & PA_IO_EVENT_INPUT) {
struct input_event ev;
if (pa_loop_read(u->fd, &ev, sizeof(ev)) <= 0) {
pa_log(__FILE__": failed to read from event device: %s\n", strerror(errno));
goto fail;
}
if (ev.type == EV_KEY && (ev.value == 1 || ev.value == 2)) {
enum { INVALID, UP, DOWN, MUTE_TOGGLE } volchange = INVALID;
pa_log_debug(__FILE__": key code=%u, value=%u\n", ev.code, ev.value);
switch (ev.code) {
case KEY_VOLUMEDOWN: volchange = DOWN; break;
case KEY_VOLUMEUP: volchange = UP; break;
case KEY_MUTE: volchange = MUTE_TOGGLE; break;
}
if (volchange != INVALID) {
pa_sink *s;
if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1)))
pa_log(__FILE__": failed to get sink '%s'\n", u->sink_name);
else {
pa_volume_t v = pa_cvolume_avg(pa_sink_get_volume(s, PA_MIXER_HARDWARE));
pa_cvolume cv;
#define DELTA (PA_VOLUME_NORM/20)
switch (volchange) {
case UP:
v += DELTA;
break;
case DOWN:
if (v > DELTA)
v -= DELTA;
else
v = PA_VOLUME_MUTED;
break;
case MUTE_TOGGLE: {
if (v > 0) {
u->mute_toggle_save = v;
v = PA_VOLUME_MUTED;
} else
v = u->mute_toggle_save;
}
default:
;
}
pa_cvolume_set(&cv, PA_CHANNELS_MAX, v);
pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv);
}
}
}
}
return;
fail:
u->module->core->mainloop->io_free(u->io);
u->io = NULL;
pa_module_unload_request(u->module);
}
#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8)))
int pa__init(pa_core *c, pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u;
int version;
struct _input_id input_id;
char name[256];
uint8_t evtype_bitmask[EV_MAX/8 + 1];
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": Failed to parse module arguments\n");
goto fail;
}
m->userdata = u = pa_xmalloc(sizeof(struct userdata));
u->module = m;
u->io = NULL;
u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
u->fd = -1;
u->mute_toggle_save = 0;
if ((u->fd = open(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), O_RDONLY)) < 0) {
pa_log(__FILE__": failed to open evdev device: %s\n", strerror(errno));
goto fail;
}
if (ioctl(u->fd, EVIOCGVERSION, &version) < 0) {
pa_log(__FILE__": EVIOCGVERSION failed: %s\n", strerror(errno));
goto fail;
}
pa_log_info(__FILE__": evdev driver version %i.%i.%i\n", version >> 16, (version >> 8) & 0xff, version & 0xff);
if(ioctl(u->fd, EVIOCGID, &input_id)) {
pa_log(__FILE__": EVIOCGID failed: %s\n", strerror(errno));
goto fail;
}
pa_log_info(__FILE__": evdev vendor 0x%04hx product 0x%04hx version 0x%04hx bustype %u\n",
input_id.vendor, input_id.product, input_id.version, input_id.bustype);
if(ioctl(u->fd, EVIOCGNAME(sizeof(name)), name) < 0) {
pa_log(__FILE__": EVIOCGNAME failed: %s\n", strerror(errno));
goto fail;
}
pa_log_info(__FILE__": evdev device name: %s\n", name);
memset(evtype_bitmask, 0, sizeof(evtype_bitmask));
if (ioctl(u->fd, EVIOCGBIT(0, EV_MAX), evtype_bitmask) < 0) {
pa_log(__FILE__": EVIOCGBIT failed: %s\n", strerror(errno));
goto fail;
}
if (!test_bit(EV_KEY, evtype_bitmask)) {
pa_log(__FILE__": device has no keys.\n");
goto fail;
}
u->io = c->mainloop->io_new(c->mainloop, u->fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u);
pa_modargs_free(ma);
return 0;
fail:
if (ma)
pa_modargs_free(ma);
pa__done(c, m);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u;
assert(c);
assert(m);
if (!(u = m->userdata))
return;
if (u->io)
m->core->mainloop->io_free(u->io);
if (u->fd >= 0)
close(u->fd);
pa_xfree(u->sink_name);
pa_xfree(u);
}

View file

@ -0,0 +1,85 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <polypcore/module.h>
#include <polypcore/iochannel.h>
#include <polypcore/modargs.h>
#include <polypcore/protocol-native.h>
#include <polypcore/log.h>
#include "module-native-protocol-fd-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("Native protocol autospawn helper")
PA_MODULE_VERSION(PACKAGE_VERSION)
static const char* const valid_modargs[] = {
"fd",
"public",
"cookie",
NULL,
};
int pa__init(pa_core *c, pa_module*m) {
pa_iochannel *io;
pa_modargs *ma;
int fd, r = -1;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments.\n");
goto finish;
}
if (pa_modargs_get_value_s32(ma, "fd", &fd) < 0) {
pa_log(__FILE__": invalid file descriptor.\n");
goto finish;
}
io = pa_iochannel_new(c->mainloop, fd, fd);
if (!(m->userdata = pa_protocol_native_new_iochannel(c, io, m, ma))) {
pa_iochannel_free(io);
goto finish;
}
r = 0;
finish:
if (ma)
pa_modargs_free(ma);
return r;
}
void pa__done(pa_core *c, pa_module*m) {
assert(c && m);
pa_protocol_native_free(m->userdata);
}

View file

@ -0,0 +1,150 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <sys/stat.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <polypcore/iochannel.h>
#include <polypcore/sink.h>
#include <polypcore/module.h>
#include <polypcore/util.h>
#include <polypcore/modargs.h>
#include <polypcore/xmalloc.h>
#include <polypcore/log.h>
#include "module-null-sink-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("Clocked NULL sink")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("format=<sample format> channels=<number of channels> rate=<sample rate> sink_name=<name of sink>")
#define DEFAULT_SINK_NAME "null"
struct userdata {
pa_core *core;
pa_module *module;
pa_sink *sink;
pa_time_event *time_event;
size_t block_size;
};
static const char* const valid_modargs[] = {
"rate",
"format",
"channels",
"sink_name",
NULL
};
static void time_callback(pa_mainloop_api *m, pa_time_event*e, const struct timeval *tv, void *userdata) {
struct userdata *u = userdata;
pa_memchunk chunk;
struct timeval ntv = *tv;
size_t l;
assert(u);
if (pa_sink_render(u->sink, u->block_size, &chunk) >= 0) {
l = chunk.length;
pa_memblock_unref(chunk.memblock);
} else
l = u->block_size;
pa_timeval_add(&ntv, pa_bytes_to_usec(l, &u->sink->sample_spec));
m->time_restart(e, &ntv);
}
int pa__init(pa_core *c, pa_module*m) {
struct userdata *u = NULL;
pa_sample_spec ss;
pa_modargs *ma = NULL;
struct timeval tv;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments.\n");
goto fail;
}
ss = c->default_sample_spec;
if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
pa_log(__FILE__": invalid sample format specification.\n");
goto fail;
}
u = pa_xmalloc0(sizeof(struct userdata));
u->core = c;
u->module = m;
m->userdata = u;
if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) {
pa_log(__FILE__": failed to create sink.\n");
goto fail;
}
u->sink->userdata = u;
pa_sink_set_owner(u->sink, m);
u->sink->description = pa_sprintf_malloc("NULL sink");
pa_gettimeofday(&tv);
u->time_event = c->mainloop->time_new(c->mainloop, &tv, time_callback, u);
u->block_size = pa_bytes_per_second(&ss) / 10;
pa_modargs_free(ma);
return 0;
fail:
if (ma)
pa_modargs_free(ma);
pa__done(c, m);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u;
assert(c && m);
if (!(u = m->userdata))
return;
pa_sink_disconnect(u->sink);
pa_sink_unref(u->sink);
u->core->mainloop->time_free(u->time_event);
pa_xfree(u);
}

View file

@ -0,0 +1,426 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <sys/mman.h>
#include <polypcore/iochannel.h>
#include <polypcore/sink.h>
#include <polypcore/source.h>
#include <polypcore/module.h>
#include <polypcore/oss-util.h>
#include <polypcore/sample-util.h>
#include <polypcore/util.h>
#include <polypcore/modargs.h>
#include <polypcore/xmalloc.h>
#include <polypcore/log.h>
#include "module-oss-mmap-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("OSS Sink/Source (mmap)")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> device=<OSS device> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
struct userdata {
pa_sink *sink;
pa_source *source;
pa_core *core;
pa_sample_spec sample_spec;
size_t in_fragment_size, out_fragment_size, in_fragments, out_fragments, out_fill;
int fd;
void *in_mmap, *out_mmap;
size_t in_mmap_length, out_mmap_length;
pa_io_event *io_event;
pa_memblock **in_memblocks, **out_memblocks;
unsigned out_current, in_current;
pa_module *module;
};
static const char* const valid_modargs[] = {
"sink_name",
"source_name",
"device",
"record",
"playback",
"fragments",
"fragment_size",
"format",
"rate",
"channels",
NULL
};
#define DEFAULT_SINK_NAME "oss_output"
#define DEFAULT_SOURCE_NAME "oss_input"
#define DEFAULT_DEVICE "/dev/dsp"
static void update_usage(struct userdata *u) {
pa_module_set_used(u->module,
(u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
(u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0) +
(u->source ? pa_idxset_size(u->source->outputs) : 0));
}
static void out_fill_memblocks(struct userdata *u, unsigned n) {
assert(u && u->out_memblocks);
while (n > 0) {
pa_memchunk chunk;
if (u->out_memblocks[u->out_current])
pa_memblock_unref_fixed(u->out_memblocks[u->out_current]);
chunk.memblock = u->out_memblocks[u->out_current] = pa_memblock_new_fixed((uint8_t*)u->out_mmap+u->out_fragment_size*u->out_current, u->out_fragment_size, 1, u->core->memblock_stat);
assert(chunk.memblock);
chunk.length = chunk.memblock->length;
chunk.index = 0;
pa_sink_render_into_full(u->sink, &chunk);
u->out_current++;
while (u->out_current >= u->out_fragments)
u->out_current -= u->out_fragments;
n--;
}
}
static void do_write(struct userdata *u) {
struct count_info info;
assert(u && u->sink);
update_usage(u);
if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) {
pa_log(__FILE__": SNDCTL_DSP_GETOPTR: %s\n", strerror(errno));
return;
}
u->out_fill = (u->out_fragment_size * u->out_fragments) - (info.ptr % u->out_fragment_size);
if (!info.blocks)
return;
out_fill_memblocks(u, info.blocks);
}
static void in_post_memblocks(struct userdata *u, unsigned n) {
assert(u && u->in_memblocks);
while (n > 0) {
pa_memchunk chunk;
if (!u->in_memblocks[u->in_current]) {
chunk.memblock = u->in_memblocks[u->in_current] = pa_memblock_new_fixed((uint8_t*) u->in_mmap+u->in_fragment_size*u->in_current, u->in_fragment_size, 1, u->core->memblock_stat);
chunk.length = chunk.memblock->length;
chunk.index = 0;
pa_source_post(u->source, &chunk);
}
u->in_current++;
while (u->in_current >= u->in_fragments)
u->in_current -= u->in_fragments;
n--;
}
}
static void in_clear_memblocks(struct userdata*u, unsigned n) {
unsigned i = u->in_current;
assert(u && u->in_memblocks);
if (n > u->in_fragments)
n = u->in_fragments;
while (n > 0) {
if (u->in_memblocks[i]) {
pa_memblock_unref_fixed(u->in_memblocks[i]);
u->in_memblocks[i] = NULL;
}
i++;
while (i >= u->in_fragments)
i -= u->in_fragments;
n--;
}
}
static void do_read(struct userdata *u) {
struct count_info info;
assert(u && u->source);
update_usage(u);
if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) {
pa_log(__FILE__": SNDCTL_DSP_GETIPTR: %s\n", strerror(errno));
return;
}
if (!info.blocks)
return;
in_post_memblocks(u, info.blocks);
in_clear_memblocks(u, u->in_fragments/2);
}
static void io_callback(pa_mainloop_api *m, pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t f, void *userdata) {
struct userdata *u = userdata;
assert (u && u->core->mainloop == m && u->io_event == e);
if (f & PA_IO_EVENT_INPUT)
do_read(u);
if (f & PA_IO_EVENT_OUTPUT)
do_write(u);
}
static pa_usec_t sink_get_latency_cb(pa_sink *s) {
struct userdata *u = s->userdata;
assert(s && u);
do_write(u);
return pa_bytes_to_usec(u->out_fill, &s->sample_spec);
}
int pa__init(pa_core *c, pa_module*m) {
struct audio_buf_info info;
struct userdata *u = NULL;
const char *p;
int nfrags, frag_size;
int mode, caps;
int enable_bits = 0, zero = 0;
int playback = 1, record = 1;
pa_modargs *ma = NULL;
assert(c && m);
m->userdata = u = pa_xmalloc0(sizeof(struct userdata));
u->module = m;
u->fd = -1;
u->core = c;
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments.\n");
goto fail;
}
if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
pa_log(__FILE__": record= and playback= expect numeric arguments.\n");
goto fail;
}
if (!playback && !record) {
pa_log(__FILE__": neither playback nor record enabled for device.\n");
goto fail;
}
mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0));
nfrags = 12;
frag_size = 1024;
if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
pa_log(__FILE__": failed to parse fragments arguments\n");
goto fail;
}
u->sample_spec = c->default_sample_spec;
if (pa_modargs_get_sample_spec(ma, &u->sample_spec) < 0) {
pa_log(__FILE__": failed to parse sample specification\n");
goto fail;
}
if ((u->fd = pa_oss_open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, &caps)) < 0)
goto fail;
if (!(caps & DSP_CAP_MMAP) || !(caps & DSP_CAP_REALTIME) || !(caps & DSP_CAP_TRIGGER)) {
pa_log(__FILE__": OSS device not mmap capable.\n");
goto fail;
}
pa_log(__FILE__": device opened in %s mode.\n", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR"));
if (nfrags >= 2 && frag_size >= 1)
if (pa_oss_set_fragments(u->fd, nfrags, frag_size) < 0)
goto fail;
if (pa_oss_auto_format(u->fd, &u->sample_spec) < 0)
goto fail;
if (mode != O_WRONLY) {
if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) {
pa_log(__FILE__": SNDCTL_DSP_GETISPACE: %s\n", strerror(errno));
goto fail;
}
pa_log_info(__FILE__": input -- %u fragments of size %u.\n", info.fragstotal, info.fragsize);
u->in_mmap_length = (u->in_fragment_size = info.fragsize) * (u->in_fragments = info.fragstotal);
if ((u->in_mmap = mmap(NULL, u->in_mmap_length, PROT_READ, MAP_SHARED, u->fd, 0)) == MAP_FAILED) {
if (mode == O_RDWR) {
pa_log(__FILE__": mmap failed for input. Changing to O_WRONLY mode.\n");
mode = O_WRONLY;
} else {
pa_log(__FILE__": mmap(): %s\n", strerror(errno));
goto fail;
}
} else {
u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &u->sample_spec, NULL);
assert(u->source);
u->source->userdata = u;
pa_source_set_owner(u->source, m);
u->source->description = pa_sprintf_malloc("Open Sound System PCM/mmap() on '%s'", p);
u->in_memblocks = pa_xmalloc0(sizeof(pa_memblock *)*u->in_fragments);
enable_bits |= PCM_ENABLE_INPUT;
}
}
if (mode != O_RDONLY) {
if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) {
pa_log(__FILE__": SNDCTL_DSP_GETOSPACE: %s\n", strerror(errno));
goto fail;
}
pa_log_info(__FILE__": output -- %u fragments of size %u.\n", info.fragstotal, info.fragsize);
u->out_mmap_length = (u->out_fragment_size = info.fragsize) * (u->out_fragments = info.fragstotal);
if ((u->out_mmap = mmap(NULL, u->out_mmap_length, PROT_WRITE, MAP_SHARED, u->fd, 0)) == MAP_FAILED) {
if (mode == O_RDWR) {
pa_log(__FILE__": mmap filed for output. Changing to O_RDONLY mode.\n");
mode = O_RDONLY;
} else {
pa_log(__FILE__": mmap(): %s\n", strerror(errno));
goto fail;
}
} else {
pa_silence_memory(u->out_mmap, u->out_mmap_length, &u->sample_spec);
u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &u->sample_spec, NULL);
assert(u->sink);
u->sink->get_latency = sink_get_latency_cb;
u->sink->userdata = u;
pa_sink_set_owner(u->sink, m);
u->sink->description = pa_sprintf_malloc("Open Sound System PCM/mmap() on '%s'", p);
u->out_memblocks = pa_xmalloc0(sizeof(struct memblock *)*u->out_fragments);
enable_bits |= PCM_ENABLE_OUTPUT;
}
}
zero = 0;
if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &zero) < 0) {
pa_log(__FILE__": SNDCTL_DSP_SETTRIGGER: %s\n", strerror(errno));
goto fail;
}
if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &enable_bits) < 0) {
pa_log(__FILE__": SNDCTL_DSP_SETTRIGGER: %s\n", strerror(errno));
goto fail;
}
assert(u->source || u->sink);
u->io_event = c->mainloop->io_new(c->mainloop, u->fd, (u->source ? PA_IO_EVENT_INPUT : 0) | (u->sink ? PA_IO_EVENT_OUTPUT : 0), io_callback, u);
assert(u->io_event);
pa_modargs_free(ma);
return 0;
fail:
pa__done(c, m);
if (ma)
pa_modargs_free(ma);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u;
assert(c && m);
if (!(u = m->userdata))
return;
if (u->out_memblocks) {
unsigned i;
for (i = 0; i < u->out_fragments; i++)
if (u->out_memblocks[i])
pa_memblock_unref_fixed(u->out_memblocks[i]);
pa_xfree(u->out_memblocks);
}
if (u->in_memblocks) {
unsigned i;
for (i = 0; i < u->in_fragments; i++)
if (u->in_memblocks[i])
pa_memblock_unref_fixed(u->in_memblocks[i]);
pa_xfree(u->in_memblocks);
}
if (u->in_mmap && u->in_mmap != MAP_FAILED)
munmap(u->in_mmap, u->in_mmap_length);
if (u->out_mmap && u->out_mmap != MAP_FAILED)
munmap(u->out_mmap, u->out_mmap_length);
if (u->sink) {
pa_sink_disconnect(u->sink);
pa_sink_unref(u->sink);
}
if (u->source) {
pa_source_disconnect(u->source);
pa_source_unref(u->source);
}
if (u->io_event)
u->core->mainloop->io_free(u->io_event);
if (u->fd >= 0)
close(u->fd);
pa_xfree(u);
}

468
src/modules/module-oss.c Normal file
View file

@ -0,0 +1,468 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <polypcore/iochannel.h>
#include <polypcore/sink.h>
#include <polypcore/source.h>
#include <polypcore/module.h>
#include <polypcore/oss-util.h>
#include <polypcore/sample-util.h>
#include <polypcore/util.h>
#include <polypcore/modargs.h>
#include <polypcore/xmalloc.h>
#include <polypcore/log.h>
#include "module-oss-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("OSS Sink/Source")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> device=<OSS device> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
struct userdata {
pa_sink *sink;
pa_source *source;
pa_iochannel *io;
pa_core *core;
pa_memchunk memchunk, silence;
uint32_t in_fragment_size, out_fragment_size, sample_size;
int use_getospace, use_getispace;
int fd;
pa_module *module;
};
static const char* const valid_modargs[] = {
"sink_name",
"source_name",
"device",
"record",
"playback",
"fragments",
"fragment_size",
"format",
"rate",
"channels",
NULL
};
#define DEFAULT_SINK_NAME "oss_output"
#define DEFAULT_SOURCE_NAME "oss_input"
#define DEFAULT_DEVICE "/dev/dsp"
static void update_usage(struct userdata *u) {
pa_module_set_used(u->module,
(u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
(u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0) +
(u->source ? pa_idxset_size(u->source->outputs) : 0));
}
static void do_write(struct userdata *u) {
pa_memchunk *memchunk;
ssize_t r;
size_t l;
int loop = 0;
assert(u);
if (!u->sink || !pa_iochannel_is_writable(u->io))
return;
update_usage(u);
l = u->out_fragment_size;
if (u->use_getospace) {
audio_buf_info info;
if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0)
u->use_getospace = 0;
else {
if (info.bytes/l > 0) {
l = (info.bytes/l)*l;
loop = 1;
}
}
}
do {
memchunk = &u->memchunk;
if (!memchunk->length)
if (pa_sink_render(u->sink, l, memchunk) < 0)
memchunk = &u->silence;
assert(memchunk->memblock);
assert(memchunk->memblock->data);
assert(memchunk->length);
if ((r = pa_iochannel_write(u->io, (uint8_t*) memchunk->memblock->data + memchunk->index, memchunk->length)) < 0) {
pa_log(__FILE__": write() failed: %s\n", strerror(errno));
break;
}
if (memchunk == &u->silence)
assert(r % u->sample_size == 0);
else {
u->memchunk.index += r;
u->memchunk.length -= r;
if (u->memchunk.length <= 0) {
pa_memblock_unref(u->memchunk.memblock);
u->memchunk.memblock = NULL;
}
}
l = l > (size_t) r ? l - r : 0;
} while (loop && l > 0);
}
static void do_read(struct userdata *u) {
pa_memchunk memchunk;
ssize_t r;
size_t l;
int loop = 0;
assert(u);
if (!u->source || !pa_iochannel_is_readable(u->io) || !pa_idxset_size(u->source->outputs))
return;
update_usage(u);
l = u->in_fragment_size;
if (u->use_getispace) {
audio_buf_info info;
if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0)
u->use_getispace = 0;
else {
if (info.bytes/l > 0) {
l = (info.bytes/l)*l;
loop = 1;
}
}
}
do {
memchunk.memblock = pa_memblock_new(l, u->core->memblock_stat);
assert(memchunk.memblock);
if ((r = pa_iochannel_read(u->io, memchunk.memblock->data, memchunk.memblock->length)) < 0) {
pa_memblock_unref(memchunk.memblock);
if (errno != EAGAIN)
pa_log(__FILE__": read() failed: %s\n", strerror(errno));
break;
}
assert(r <= (ssize_t) memchunk.memblock->length);
memchunk.length = memchunk.memblock->length = r;
memchunk.index = 0;
pa_source_post(u->source, &memchunk);
pa_memblock_unref(memchunk.memblock);
l = l > (size_t) r ? l - r : 0;
} while (loop && l > 0);
}
static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) {
struct userdata *u = userdata;
assert(u);
do_write(u);
do_read(u);
}
static void source_notify_cb(pa_source *s) {
struct userdata *u = s->userdata;
assert(u);
do_read(u);
}
static pa_usec_t sink_get_latency_cb(pa_sink *s) {
pa_usec_t r = 0;
int arg;
struct userdata *u = s->userdata;
assert(s && u && u->sink);
if (ioctl(u->fd, SNDCTL_DSP_GETODELAY, &arg) < 0) {
pa_log_info(__FILE__": device doesn't support SNDCTL_DSP_GETODELAY: %s\n", strerror(errno));
s->get_latency = NULL;
return 0;
}
r += pa_bytes_to_usec(arg, &s->sample_spec);
if (u->memchunk.memblock)
r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec);
return r;
}
static pa_usec_t source_get_latency_cb(pa_source *s) {
struct userdata *u = s->userdata;
audio_buf_info info;
assert(s && u && u->source);
if (!u->use_getispace)
return 0;
if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) {
u->use_getispace = 0;
return 0;
}
if (info.bytes <= 0)
return 0;
return pa_bytes_to_usec(info.bytes, &s->sample_spec);
}
static int sink_get_hw_volume(pa_sink *s) {
struct userdata *u = s->userdata;
char cv[PA_CVOLUME_SNPRINT_MAX];
unsigned vol;
if (ioctl(u->fd, SOUND_MIXER_READ_PCM, &vol) < 0) {
pa_log_info(__FILE__": device doesn't support reading mixer settings: %s\n", strerror(errno));
s->get_hw_volume = NULL;
return -1;
}
s->hw_volume.values[0] = ((vol & 0xFF) * PA_VOLUME_NORM) / 100;
if ((s->hw_volume.channels = s->sample_spec.channels) >= 2)
s->hw_volume.values[1] = (((vol >> 8) & 0xFF) * PA_VOLUME_NORM) / 100;
pa_log_info(__FILE__": Read mixer settings: %s\n", pa_cvolume_snprint(cv, sizeof(cv), &s->hw_volume));
return 0;
}
static int sink_set_hw_volume(pa_sink *s) {
struct userdata *u = s->userdata;
char cv[PA_CVOLUME_SNPRINT_MAX];
unsigned vol;
vol = (s->hw_volume.values[0]*100)/PA_VOLUME_NORM;
if (s->sample_spec.channels >= 2)
vol |= ((s->hw_volume.values[1]*100)/PA_VOLUME_NORM) << 8;
if (ioctl(u->fd, SOUND_MIXER_WRITE_PCM, &vol) < 0) {
pa_log_info(__FILE__": device doesn't support writing mixer settings: %s\n", strerror(errno));
s->set_hw_volume = NULL;
return -1;
}
pa_log_info(__FILE__": Wrote mixer settings: %s\n", pa_cvolume_snprint(cv, sizeof(cv), &s->hw_volume));
return 0;
}
int pa__init(pa_core *c, pa_module*m) {
struct audio_buf_info info;
struct userdata *u = NULL;
const char *p;
int fd = -1;
int nfrags, frag_size, in_frag_size, out_frag_size;
int mode;
int record = 1, playback = 1;
pa_sample_spec ss;
pa_modargs *ma = NULL;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments.\n");
goto fail;
}
if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
pa_log(__FILE__": record= and playback= expect numeric argument.\n");
goto fail;
}
if (!playback && !record) {
pa_log(__FILE__": neither playback nor record enabled for device.\n");
goto fail;
}
mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0));
nfrags = 12;
frag_size = 1024;
if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
pa_log(__FILE__": failed to parse fragments arguments\n");
goto fail;
}
ss = c->default_sample_spec;
if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
pa_log(__FILE__": failed to parse sample specification\n");
goto fail;
}
if ((fd = pa_oss_open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, NULL)) < 0)
goto fail;
pa_log_info(__FILE__": device opened in %s mode.\n", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR"));
if (nfrags >= 2 && frag_size >= 1)
if (pa_oss_set_fragments(fd, nfrags, frag_size) < 0)
goto fail;
if (pa_oss_auto_format(fd, &ss) < 0)
goto fail;
if (ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) < 0) {
pa_log(__FILE__": SNDCTL_DSP_GETBLKSIZE: %s\n", strerror(errno));
goto fail;
}
assert(frag_size);
in_frag_size = out_frag_size = frag_size;
u = pa_xmalloc(sizeof(struct userdata));
u->core = c;
u->use_getospace = u->use_getispace = 0;
if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) >= 0) {
pa_log_info(__FILE__": input -- %u fragments of size %u.\n", info.fragstotal, info.fragsize);
in_frag_size = info.fragsize;
u->use_getispace = 1;
}
if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) {
pa_log_info(__FILE__": output -- %u fragments of size %u.\n", info.fragstotal, info.fragsize);
out_frag_size = info.fragsize;
u->use_getospace = 1;
}
if (mode != O_WRONLY) {
u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
assert(u->source);
u->source->userdata = u;
u->source->notify = source_notify_cb;
u->source->get_latency = source_get_latency_cb;
pa_source_set_owner(u->source, m);
u->source->description = pa_sprintf_malloc("Open Sound System PCM on '%s'", p);
} else
u->source = NULL;
if (mode != O_RDONLY) {
u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
assert(u->sink);
u->sink->get_latency = sink_get_latency_cb;
u->sink->get_hw_volume = sink_get_hw_volume;
u->sink->set_hw_volume = sink_set_hw_volume;
u->sink->userdata = u;
pa_sink_set_owner(u->sink, m);
u->sink->description = pa_sprintf_malloc("Open Sound System PCM on '%s'", p);
} else
u->sink = NULL;
assert(u->source || u->sink);
u->io = pa_iochannel_new(c->mainloop, u->source ? fd : -1, u->sink ? fd : -1);
assert(u->io);
pa_iochannel_set_callback(u->io, io_callback, u);
u->fd = fd;
u->memchunk.memblock = NULL;
u->memchunk.length = 0;
u->sample_size = pa_frame_size(&ss);
u->out_fragment_size = out_frag_size;
u->in_fragment_size = in_frag_size;
u->silence.memblock = pa_memblock_new(u->silence.length = u->out_fragment_size, u->core->memblock_stat);
assert(u->silence.memblock);
pa_silence_memblock(u->silence.memblock, &ss);
u->silence.index = 0;
u->module = m;
m->userdata = u;
pa_modargs_free(ma);
/*
* Some crappy drivers do not start the recording until we read something.
* Without this snippet, poll will never register the fd as ready.
*/
if (u->source) {
char buf[u->sample_size];
read(u->fd, buf, u->sample_size);
}
/* Read mixer settings */
if (u->sink)
sink_get_hw_volume(u->sink);
return 0;
fail:
if (fd >= 0)
close(fd);
if (ma)
pa_modargs_free(ma);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u;
assert(c && m);
if (!(u = m->userdata))
return;
if (u->memchunk.memblock)
pa_memblock_unref(u->memchunk.memblock);
if (u->silence.memblock)
pa_memblock_unref(u->silence.memblock);
if (u->sink) {
pa_sink_disconnect(u->sink);
pa_sink_unref(u->sink);
}
if (u->source) {
pa_source_disconnect(u->source);
pa_source_unref(u->source);
}
pa_iochannel_free(u->io);
pa_xfree(u);
}

View file

@ -0,0 +1,237 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <sys/stat.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <polypcore/iochannel.h>
#include <polypcore/sink.h>
#include <polypcore/module.h>
#include <polypcore/util.h>
#include <polypcore/modargs.h>
#include <polypcore/xmalloc.h>
#include <polypcore/log.h>
#include "module-pipe-sink-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("UNIX pipe sink")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("sink_name=<name for the sink> file=<path of the FIFO> format=<sample format> channels=<number of channels> rate=<sample rate>")
#define DEFAULT_FIFO_NAME "/tmp/music.output"
#define DEFAULT_SINK_NAME "fifo_output"
struct userdata {
pa_core *core;
char *filename;
pa_sink *sink;
pa_iochannel *io;
pa_defer_event *defer_event;
pa_memchunk memchunk;
pa_module *module;
};
static const char* const valid_modargs[] = {
"file",
"rate",
"format",
"channels",
"sink_name",
NULL
};
static void do_write(struct userdata *u) {
ssize_t r;
assert(u);
u->core->mainloop->defer_enable(u->defer_event, 0);
if (!pa_iochannel_is_writable(u->io))
return;
pa_module_set_used(u->module, pa_idxset_size(u->sink->inputs) + pa_idxset_size(u->sink->monitor_source->outputs));
if (!u->memchunk.length)
if (pa_sink_render(u->sink, PIPE_BUF, &u->memchunk) < 0)
return;
assert(u->memchunk.memblock && u->memchunk.length);
if ((r = pa_iochannel_write(u->io, (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length)) < 0) {
pa_log(__FILE__": write() failed: %s\n", strerror(errno));
return;
}
u->memchunk.index += r;
u->memchunk.length -= r;
if (u->memchunk.length <= 0) {
pa_memblock_unref(u->memchunk.memblock);
u->memchunk.memblock = NULL;
}
}
static void notify_cb(pa_sink*s) {
struct userdata *u = s->userdata;
assert(s && u);
if (pa_iochannel_is_writable(u->io))
u->core->mainloop->defer_enable(u->defer_event, 1);
}
static pa_usec_t get_latency_cb(pa_sink *s) {
struct userdata *u = s->userdata;
assert(s && u);
return u->memchunk.memblock ? pa_bytes_to_usec(u->memchunk.length, &s->sample_spec) : 0;
}
static void defer_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC_UNUSED pa_defer_event*e, void *userdata) {
struct userdata *u = userdata;
assert(u);
do_write(u);
}
static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) {
struct userdata *u = userdata;
assert(u);
do_write(u);
}
int pa__init(pa_core *c, pa_module*m) {
struct userdata *u = NULL;
struct stat st;
const char *p;
int fd = -1;
pa_sample_spec ss;
pa_modargs *ma = NULL;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments\n");
goto fail;
}
ss = c->default_sample_spec;
if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
pa_log(__FILE__": invalid sample format specification\n");
goto fail;
}
mkfifo(p = pa_modargs_get_value(ma, "file", DEFAULT_FIFO_NAME), 0777);
if ((fd = open(p, O_RDWR)) < 0) {
pa_log(__FILE__": open('%s'): %s\n", p, strerror(errno));
goto fail;
}
pa_fd_set_cloexec(fd, 1);
if (fstat(fd, &st) < 0) {
pa_log(__FILE__": fstat('%s'): %s\n", p, strerror(errno));
goto fail;
}
if (!S_ISFIFO(st.st_mode)) {
pa_log(__FILE__": '%s' is not a FIFO.\n", p);
goto fail;
}
u = pa_xmalloc0(sizeof(struct userdata));
u->filename = pa_xstrdup(p);
u->core = c;
u->module = m;
m->userdata = u;
if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) {
pa_log(__FILE__": failed to create sink.\n");
goto fail;
}
u->sink->notify = notify_cb;
u->sink->get_latency = get_latency_cb;
u->sink->userdata = u;
pa_sink_set_owner(u->sink, m);
u->sink->description = pa_sprintf_malloc("Unix FIFO sink '%s'", p);
assert(u->sink->description);
u->io = pa_iochannel_new(c->mainloop, -1, fd);
assert(u->io);
pa_iochannel_set_callback(u->io, io_callback, u);
u->memchunk.memblock = NULL;
u->memchunk.length = 0;
u->defer_event = c->mainloop->defer_new(c->mainloop, defer_callback, u);
assert(u->defer_event);
c->mainloop->defer_enable(u->defer_event, 0);
pa_modargs_free(ma);
return 0;
fail:
if (ma)
pa_modargs_free(ma);
if (fd >= 0)
close(fd);
pa__done(c, m);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u;
assert(c && m);
if (!(u = m->userdata))
return;
if (u->memchunk.memblock)
pa_memblock_unref(u->memchunk.memblock);
pa_sink_disconnect(u->sink);
pa_sink_unref(u->sink);
pa_iochannel_free(u->io);
u->core->mainloop->defer_free(u->defer_event);
assert(u->filename);
unlink(u->filename);
pa_xfree(u->filename);
pa_xfree(u);
}

View file

@ -0,0 +1,210 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <sys/stat.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <polypcore/iochannel.h>
#include <polypcore/source.h>
#include <polypcore/module.h>
#include <polypcore/util.h>
#include <polypcore/modargs.h>
#include <polypcore/xmalloc.h>
#include <polypcore/log.h>
#include "module-pipe-source-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("UNIX pipe source")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("source_name=<name for the source> file=<path of the FIFO> format=<sample format> channels=<number of channels> rate=<sample rate>")
#define DEFAULT_FIFO_NAME "/tmp/music.input"
#define DEFAULT_SOURCE_NAME "fifo_input"
struct userdata {
pa_core *core;
char *filename;
pa_source *source;
pa_iochannel *io;
pa_module *module;
pa_memchunk chunk;
};
static const char* const valid_modargs[] = {
"file",
"rate",
"channels",
"format",
"source_name",
NULL
};
static void do_read(struct userdata *u) {
ssize_t r;
pa_memchunk chunk;
assert(u);
if (!pa_iochannel_is_readable(u->io))
return;
pa_module_set_used(u->module, pa_idxset_size(u->source->outputs));
if (!u->chunk.memblock) {
u->chunk.memblock = pa_memblock_new(1024, u->core->memblock_stat);
u->chunk.index = chunk.length = 0;
}
assert(u->chunk.memblock && u->chunk.memblock->length > u->chunk.index);
if ((r = pa_iochannel_read(u->io, (uint8_t*) u->chunk.memblock->data + u->chunk.index, u->chunk.memblock->length - u->chunk.index)) <= 0) {
pa_log(__FILE__": read() failed: %s\n", strerror(errno));
return;
}
u->chunk.length = r;
pa_source_post(u->source, &u->chunk);
u->chunk.index += r;
if (u->chunk.index >= u->chunk.memblock->length) {
u->chunk.index = u->chunk.length = 0;
pa_memblock_unref(u->chunk.memblock);
u->chunk.memblock = NULL;
}
}
static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) {
struct userdata *u = userdata;
assert(u);
do_read(u);
}
int pa__init(pa_core *c, pa_module*m) {
struct userdata *u = NULL;
struct stat st;
const char *p;
int fd = -1;
pa_sample_spec ss;
pa_modargs *ma = NULL;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments\n");
goto fail;
}
ss = c->default_sample_spec;
if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
pa_log(__FILE__": invalid sample format specification\n");
goto fail;
}
mkfifo(p = pa_modargs_get_value(ma, "file", DEFAULT_FIFO_NAME), 0777);
if ((fd = open(p, O_RDWR)) < 0) {
pa_log(__FILE__": open('%s'): %s\n", p, strerror(errno));
goto fail;
}
pa_fd_set_cloexec(fd, 1);
if (fstat(fd, &st) < 0) {
pa_log(__FILE__": fstat('%s'): %s\n", p, strerror(errno));
goto fail;
}
if (!S_ISFIFO(st.st_mode)) {
pa_log(__FILE__": '%s' is not a FIFO.\n", p);
goto fail;
}
u = pa_xmalloc0(sizeof(struct userdata));
u->filename = pa_xstrdup(p);
u->core = c;
if (!(u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL))) {
pa_log(__FILE__": failed to create source.\n");
goto fail;
}
u->source->userdata = u;
pa_source_set_owner(u->source, m);
u->source->description = pa_sprintf_malloc("Unix FIFO source '%s'", p);
assert(u->source->description);
u->io = pa_iochannel_new(c->mainloop, fd, -1);
assert(u->io);
pa_iochannel_set_callback(u->io, io_callback, u);
u->chunk.memblock = NULL;
u->chunk.index = u->chunk.length = 0;
u->module = m;
m->userdata = u;
pa_modargs_free(ma);
return 0;
fail:
if (ma)
pa_modargs_free(ma);
if (fd >= 0)
close(fd);
pa__done(c, m);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u;
assert(c && m);
if (!(u = m->userdata))
return;
if (u->chunk.memblock)
pa_memblock_unref(u->chunk.memblock);
pa_source_disconnect(u->source);
pa_source_unref(u->source);
pa_iochannel_free(u->io);
assert(u->filename);
unlink(u->filename);
pa_xfree(u->filename);
pa_xfree(u);
}

View file

@ -0,0 +1,261 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <limits.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#include <polypcore/winsock.h>
#include <polypcore/module.h>
#include <polypcore/socket-server.h>
#include <polypcore/socket-util.h>
#include <polypcore/util.h>
#include <polypcore/modargs.h>
#include <polypcore/log.h>
#include <polypcore/native-common.h>
#ifdef USE_TCP_SOCKETS
#define SOCKET_DESCRIPTION "(TCP sockets)"
#define SOCKET_USAGE "port=<TCP port number> loopback=<listen on loopback device only?>"
#elif defined(USE_TCP6_SOCKETS)
#define SOCKET_DESCRIPTION "(TCP/IPv6 sockets)"
#define SOCKET_USAGE "port=<TCP port number> loopback=<listen on loopback device only?>"
#else
#define SOCKET_DESCRIPTION "(UNIX sockets)"
#define SOCKET_USAGE "socket=<path to UNIX socket>"
#endif
#if defined(USE_PROTOCOL_SIMPLE)
#include <polypcore/protocol-simple.h>
#define protocol_new pa_protocol_simple_new
#define protocol_free pa_protocol_simple_free
#define TCPWRAP_SERVICE "polypaudio-simple"
#define IPV4_PORT 4711
#define UNIX_SOCKET "simple"
#define MODULE_ARGUMENTS "rate", "format", "channels", "sink", "source", "playback", "record",
#if defined(USE_TCP_SOCKETS)
#include "module-simple-protocol-tcp-symdef.h"
#elif defined(USE_TCP6_SOCKETS)
#include "module-simple-protocol-tcp6-symdef.h"
#else
#include "module-simple-protocol-unix-symdef.h"
#endif
PA_MODULE_DESCRIPTION("Simple protocol "SOCKET_DESCRIPTION)
PA_MODULE_USAGE("rate=<sample rate> format=<sample format> channels=<number of channels> sink=<sink to connect to> source=<source to connect to> playback=<enable playback?> record=<enable record?> "SOCKET_USAGE)
#elif defined(USE_PROTOCOL_CLI)
#include <polypcore/protocol-cli.h>
#define protocol_new pa_protocol_cli_new
#define protocol_free pa_protocol_cli_free
#define TCPWRAP_SERVICE "polypaudio-cli"
#define IPV4_PORT 4712
#define UNIX_SOCKET "cli"
#define MODULE_ARGUMENTS
#ifdef USE_TCP_SOCKETS
#include "module-cli-protocol-tcp-symdef.h"
#elif defined(USE_TCP6_SOCKETS)
#include "module-cli-protocol-tcp6-symdef.h"
#else
#include "module-cli-protocol-unix-symdef.h"
#endif
PA_MODULE_DESCRIPTION("Command line interface protocol "SOCKET_DESCRIPTION)
PA_MODULE_USAGE(SOCKET_USAGE)
#elif defined(USE_PROTOCOL_HTTP)
#include <polypcore/protocol-http.h>
#define protocol_new pa_protocol_http_new
#define protocol_free pa_protocol_http_free
#define TCPWRAP_SERVICE "polypaudio-http"
#define IPV4_PORT 4714
#define UNIX_SOCKET "http"
#define MODULE_ARGUMENTS
#ifdef USE_TCP_SOCKETS
#include "module-http-protocol-tcp-symdef.h"
#elif defined(USE_TCP6_SOCKETS)
#include "module-http-protocol-tcp6-symdef.h"
#else
#include "module-http-protocol-unix-symdef.h"
#endif
PA_MODULE_DESCRIPTION("HTTP "SOCKET_DESCRIPTION)
PA_MODULE_USAGE(SOCKET_USAGE)
#elif defined(USE_PROTOCOL_NATIVE)
#include <polypcore/protocol-native.h>
#define protocol_new pa_protocol_native_new
#define protocol_free pa_protocol_native_free
#define TCPWRAP_SERVICE "polypaudio-native"
#define IPV4_PORT PA_NATIVE_DEFAULT_PORT
#define UNIX_SOCKET PA_NATIVE_DEFAULT_UNIX_SOCKET
#define MODULE_ARGUMENTS "public", "cookie",
#ifdef USE_TCP_SOCKETS
#include "module-native-protocol-tcp-symdef.h"
#elif defined(USE_TCP6_SOCKETS)
#include "module-native-protocol-tcp6-symdef.h"
#else
#include "module-native-protocol-unix-symdef.h"
#endif
PA_MODULE_DESCRIPTION("Native protocol "SOCKET_DESCRIPTION)
PA_MODULE_USAGE("public=<don't check for cookies?> cookie=<path to cookie file> "SOCKET_USAGE)
#elif defined(USE_PROTOCOL_ESOUND)
#include <polypcore/protocol-esound.h>
#include <polypcore/esound.h>
#define protocol_new pa_protocol_esound_new
#define protocol_free pa_protocol_esound_free
#define TCPWRAP_SERVICE "esound"
#define IPV4_PORT ESD_DEFAULT_PORT
#define UNIX_SOCKET ESD_UNIX_SOCKET_NAME
#define MODULE_ARGUMENTS "sink", "source", "public", "cookie",
#ifdef USE_TCP_SOCKETS
#include "module-esound-protocol-tcp-symdef.h"
#elif defined(USE_TCP6_SOCKETS)
#include "module-esound-protocol-tcp6-symdef.h"
#else
#include "module-esound-protocol-unix-symdef.h"
#endif
PA_MODULE_DESCRIPTION("ESOUND protocol "SOCKET_DESCRIPTION)
PA_MODULE_USAGE("sink=<sink to connect to> source=<source to connect to> public=<don't check for cookies?> cookie=<path to cookie file> "SOCKET_USAGE)
#else
#error "Broken build system"
#endif
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_VERSION(PACKAGE_VERSION)
static const char* const valid_modargs[] = {
MODULE_ARGUMENTS
#if defined(USE_TCP_SOCKETS) || defined(USE_TCP6_SOCKETS)
"port",
"loopback",
#else
"socket",
#endif
NULL
};
static pa_socket_server *create_socket_server(pa_core *c, pa_modargs *ma) {
pa_socket_server *s;
#if defined(USE_TCP_SOCKETS) || defined(USE_TCP6_SOCKETS)
int loopback = 1;
uint32_t port = IPV4_PORT;
if (pa_modargs_get_value_boolean(ma, "loopback", &loopback) < 0) {
pa_log(__FILE__": loopback= expects a boolean argument.\n");
return NULL;
}
if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port < 1 || port > 0xFFFF) {
pa_log(__FILE__": port= expects a numerical argument between 1 and 65535.\n");
return NULL;
}
#ifdef USE_TCP6_SOCKETS
if (!(s = pa_socket_server_new_ipv6(c->mainloop, loopback ? (const uint8_t*) &in6addr_loopback : (const uint8_t*) &in6addr_any, port)))
return NULL;
#else
if (!(s = pa_socket_server_new_ipv4(c->mainloop, loopback ? INADDR_LOOPBACK : INADDR_ANY, port, TCPWRAP_SERVICE)))
return NULL;
#endif
#else
int r;
const char *v;
char tmp[PATH_MAX];
v = pa_modargs_get_value(ma, "socket", UNIX_SOCKET);
assert(v);
pa_runtime_path(v, tmp, sizeof(tmp));
if (pa_make_secure_parent_dir(tmp) < 0) {
pa_log(__FILE__": Failed to create secure socket directory.\n");
return NULL;
}
if ((r = pa_unix_socket_remove_stale(tmp)) < 0) {
pa_log(__FILE__": Failed to remove stale UNIX socket '%s': %s\n", tmp, strerror(errno));
return NULL;
}
if (r)
pa_log(__FILE__": Removed stale UNIX socket '%s'.", tmp);
if (!(s = pa_socket_server_new_unix(c->mainloop, tmp)))
return NULL;
#endif
return s;
}
int pa__init(pa_core *c, pa_module*m) {
pa_socket_server *s;
pa_modargs *ma = NULL;
int ret = -1;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": Failed to parse module arguments\n");
goto finish;
}
if (!(s = create_socket_server(c, ma)))
goto finish;
if (!(m->userdata = protocol_new(c, s, m, ma))) {
pa_socket_server_unref(s);
goto finish;
}
ret = 0;
finish:
if (ma)
pa_modargs_free(ma);
return ret;
}
void pa__done(pa_core *c, pa_module*m) {
assert(c && m);
#if defined(USE_PROTOCOL_ESOUND)
if (remove (ESD_UNIX_SOCKET_NAME) != 0)
pa_log("%s: Failed to remove %s : %s.\n", __FILE__, ESD_UNIX_SOCKET_NAME, strerror (errno));
if (remove (ESD_UNIX_SOCKET_DIR) != 0)
pa_log("%s: Failed to remove %s : %s.\n", __FILE__, ESD_UNIX_SOCKET_DIR, strerror (errno));
#endif
protocol_free(m->userdata);
}

182
src/modules/module-sine.c Normal file
View file

@ -0,0 +1,182 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <polypcore/sink-input.h>
#include <polypcore/module.h>
#include <polypcore/modargs.h>
#include <polypcore/xmalloc.h>
#include <polypcore/namereg.h>
#include <polypcore/log.h>
#include "module-sine-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("Sine wave generator")
PA_MODULE_USAGE("sink=<sink to connect to> frequency=<frequency in Hz>")
PA_MODULE_VERSION(PACKAGE_VERSION)
struct userdata {
pa_core *core;
pa_module *module;
pa_sink_input *sink_input;
pa_memblock *memblock;
size_t peek_index;
};
static const char* const valid_modargs[] = {
"sink",
"frequency",
NULL,
};
static int sink_input_peek(pa_sink_input *i, pa_memchunk *chunk) {
struct userdata *u;
assert(i && chunk && i->userdata);
u = i->userdata;
chunk->memblock = pa_memblock_ref(u->memblock);
chunk->index = u->peek_index;
chunk->length = u->memblock->length - u->peek_index;
return 0;
}
static void sink_input_drop(pa_sink_input *i, const pa_memchunk *chunk, size_t length) {
struct userdata *u;
assert(i && chunk && length && i->userdata);
u = i->userdata;
assert(chunk->memblock == u->memblock && length <= u->memblock->length-u->peek_index);
u->peek_index += length;
if (u->peek_index >= u->memblock->length)
u->peek_index = 0;
}
static void sink_input_kill(pa_sink_input *i) {
struct userdata *u;
assert(i && i->userdata);
u = i->userdata;
pa_sink_input_disconnect(u->sink_input);
pa_sink_input_unref(u->sink_input);
u->sink_input = NULL;
pa_module_unload_request(u->module);
}
static void calc_sine(float *f, size_t l, float freq) {
size_t i;
l /= sizeof(float);
for (i = 0; i < l; i++)
f[i] = (float) sin((double) i/l*M_PI*2*freq)/2;
}
int pa__init(pa_core *c, pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u;
pa_sink *sink;
const char *sink_name;
pa_sample_spec ss;
uint32_t frequency;
char t[256];
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": Failed to parse module arguments\n");
goto fail;
}
m->userdata = u = pa_xmalloc(sizeof(struct userdata));
u->core = c;
u->module = m;
u->sink_input = NULL;
u->memblock = NULL;
sink_name = pa_modargs_get_value(ma, "sink", NULL);
if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK, 1))) {
pa_log(__FILE__": No such sink.\n");
goto fail;
}
ss.format = PA_SAMPLE_FLOAT32;
ss.rate = sink->sample_spec.rate;
ss.channels = 1;
frequency = 440;
if (pa_modargs_get_value_u32(ma, "frequency", &frequency) < 0 || frequency < 1 || frequency > ss.rate/2) {
pa_log(__FILE__": Invalid frequency specification\n");
goto fail;
}
u->memblock = pa_memblock_new(pa_bytes_per_second(&ss), c->memblock_stat);
calc_sine(u->memblock->data, u->memblock->length, frequency);
snprintf(t, sizeof(t), "Sine Generator at %u Hz", frequency);
if (!(u->sink_input = pa_sink_input_new(sink, __FILE__, t, &ss, NULL, 0, -1)))
goto fail;
u->sink_input->peek = sink_input_peek;
u->sink_input->drop = sink_input_drop;
u->sink_input->kill = sink_input_kill;
u->sink_input->userdata = u;
u->sink_input->owner = m;
u->peek_index = 0;
pa_modargs_free(ma);
return 0;
fail:
if (ma)
pa_modargs_free(ma);
pa__done(c, m);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u = m->userdata;
assert(c && m);
if (!u)
return;
if (u->sink_input) {
pa_sink_input_disconnect(u->sink_input);
pa_sink_input_unref(u->sink_input);
}
if (u->memblock)
pa_memblock_unref(u->memblock);
pa_xfree(u);
}

View file

@ -0,0 +1,488 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <signal.h>
#include <stropts.h>
#include <sys/conf.h>
#include <sys/audio.h>
#include <polyp/mainloop-signal.h>
#include <polypcore/iochannel.h>
#include <polypcore/sink.h>
#include <polypcore/source.h>
#include <polypcore/module.h>
#include <polypcore/sample-util.h>
#include <polypcore/util.h>
#include <polypcore/modargs.h>
#include <polypcore/xmalloc.h>
#include <polypcore/log.h>
#include "module-solaris-symdef.h"
PA_MODULE_AUTHOR("Pierre Ossman")
PA_MODULE_DESCRIPTION("Solaris Sink/Source")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> device=<OSS device> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> buffer_size=<record buffer size>")
struct userdata {
pa_sink *sink;
pa_source *source;
pa_iochannel *io;
pa_core *core;
pa_signal_event *sig;
pa_memchunk memchunk, silence;
uint32_t sample_size;
uint32_t buffer_size;
unsigned int written_bytes, read_bytes;
int fd;
pa_module *module;
};
static const char* const valid_modargs[] = {
"sink_name",
"source_name",
"device",
"record",
"playback",
"buffer_size",
"format",
"rate",
"channels",
NULL
};
#define DEFAULT_SINK_NAME "solaris_output"
#define DEFAULT_SOURCE_NAME "solaris_input"
#define DEFAULT_DEVICE "/dev/audio"
#define CHUNK_SIZE 2048
static void update_usage(struct userdata *u) {
pa_module_set_used(u->module,
(u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
(u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0) +
(u->source ? pa_idxset_size(u->source->outputs) : 0));
}
static void do_write(struct userdata *u) {
audio_info_t info;
int err;
pa_memchunk *memchunk;
size_t len;
ssize_t r;
assert(u);
if (!u->sink || !pa_iochannel_is_writable(u->io))
return;
update_usage(u);
err = ioctl(u->fd, AUDIO_GETINFO, &info);
assert(err >= 0);
/*
* Since we cannot modify the size of the output buffer we fake it
* by not filling it more than u->buffer_size.
*/
len = u->buffer_size;
len -= u->written_bytes - (info.play.samples * u->sample_size);
/*
* Do not fill more than half the buffer in one chunk since we only
* get notifications upon completion of entire chunks.
*/
if (len > (u->buffer_size / 2))
len = u->buffer_size / 2;
if (len < u->sample_size)
return;
memchunk = &u->memchunk;
if (!memchunk->length)
if (pa_sink_render(u->sink, len, memchunk) < 0)
memchunk = &u->silence;
assert(memchunk->memblock);
assert(memchunk->memblock->data);
assert(memchunk->length);
if (memchunk->length < len)
len = memchunk->length;
if ((r = pa_iochannel_write(u->io, (uint8_t*) memchunk->memblock->data + memchunk->index, len)) < 0) {
pa_log(__FILE__": write() failed: %s\n", strerror(errno));
return;
}
if (memchunk == &u->silence)
assert(r % u->sample_size == 0);
else {
u->memchunk.index += r;
u->memchunk.length -= r;
if (u->memchunk.length <= 0) {
pa_memblock_unref(u->memchunk.memblock);
u->memchunk.memblock = NULL;
}
}
u->written_bytes += r;
/*
* Write 0 bytes which will generate a SIGPOLL when "played".
*/
if (write(u->fd, NULL, 0) < 0) {
pa_log(__FILE__": write() failed: %s\n", strerror(errno));
return;
}
}
static void do_read(struct userdata *u) {
pa_memchunk memchunk;
int err, l;
ssize_t r;
assert(u);
if (!u->source || !pa_iochannel_is_readable(u->io))
return;
update_usage(u);
err = ioctl(u->fd, I_NREAD, &l);
assert(err >= 0);
memchunk.memblock = pa_memblock_new(l, u->core->memblock_stat);
assert(memchunk.memblock);
if ((r = pa_iochannel_read(u->io, memchunk.memblock->data, memchunk.memblock->length)) < 0) {
pa_memblock_unref(memchunk.memblock);
if (errno != EAGAIN)
pa_log(__FILE__": read() failed: %s\n", strerror(errno));
return;
}
assert(r <= (ssize_t) memchunk.memblock->length);
memchunk.length = memchunk.memblock->length = r;
memchunk.index = 0;
pa_source_post(u->source, &memchunk);
pa_memblock_unref(memchunk.memblock);
u->read_bytes += r;
}
static void io_callback(pa_iochannel *io, void*userdata) {
struct userdata *u = userdata;
assert(u);
do_write(u);
do_read(u);
}
void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata) {
struct userdata *u = userdata;
assert(u);
do_write(u);
}
static pa_usec_t sink_get_latency_cb(pa_sink *s) {
pa_usec_t r = 0;
audio_info_t info;
int err;
struct userdata *u = s->userdata;
assert(s && u && u->sink);
err = ioctl(u->fd, AUDIO_GETINFO, &info);
assert(err >= 0);
r += pa_bytes_to_usec(u->written_bytes, &s->sample_spec);
r -= pa_bytes_to_usec(info.play.samples * u->sample_size, &s->sample_spec);
if (u->memchunk.memblock)
r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec);
return r;
}
static pa_usec_t source_get_latency_cb(pa_source *s) {
pa_usec_t r = 0;
struct userdata *u = s->userdata;
audio_info_t info;
int err;
assert(s && u && u->source);
err = ioctl(u->fd, AUDIO_GETINFO, &info);
assert(err >= 0);
r += pa_bytes_to_usec(info.record.samples * u->sample_size, &s->sample_spec);
r -= pa_bytes_to_usec(u->read_bytes, &s->sample_spec);
return r;
}
static int pa_solaris_auto_format(int fd, int mode, pa_sample_spec *ss) {
audio_info_t info;
AUDIO_INITINFO(&info);
if (mode != O_RDONLY) {
info.play.sample_rate = ss->rate;
info.play.channels = ss->channels;
switch (ss->format) {
case PA_SAMPLE_U8:
info.play.precision = 8;
info.play.encoding = AUDIO_ENCODING_LINEAR;
break;
case PA_SAMPLE_ALAW:
info.play.precision = 8;
info.play.encoding = AUDIO_ENCODING_ALAW;
break;
case PA_SAMPLE_ULAW:
info.play.precision = 8;
info.play.encoding = AUDIO_ENCODING_ULAW;
break;
case PA_SAMPLE_S16NE:
info.play.precision = 16;
info.play.encoding = AUDIO_ENCODING_LINEAR;
break;
default:
return -1;
}
}
if (mode != O_WRONLY) {
info.record.sample_rate = ss->rate;
info.record.channels = ss->channels;
switch (ss->format) {
case PA_SAMPLE_U8:
info.record.precision = 8;
info.record.encoding = AUDIO_ENCODING_LINEAR;
break;
case PA_SAMPLE_ALAW:
info.record.precision = 8;
info.record.encoding = AUDIO_ENCODING_ALAW;
break;
case PA_SAMPLE_ULAW:
info.record.precision = 8;
info.record.encoding = AUDIO_ENCODING_ULAW;
break;
case PA_SAMPLE_S16NE:
info.record.precision = 16;
info.record.encoding = AUDIO_ENCODING_LINEAR;
break;
default:
return -1;
}
}
if (ioctl(fd, AUDIO_SETINFO, &info) < 0) {
if (errno == EINVAL)
pa_log(__FILE__": AUDIO_SETINFO: Unsupported sample format.\n");
else
pa_log(__FILE__": AUDIO_SETINFO: %s\n", strerror(errno));
return -1;
}
return 0;
}
static int pa_solaris_set_buffer(int fd, int buffer_size) {
audio_info_t info;
AUDIO_INITINFO(&info);
info.record.buffer_size = buffer_size;
if (ioctl(fd, AUDIO_SETINFO, &info) < 0) {
if (errno == EINVAL)
pa_log(__FILE__": AUDIO_SETINFO: Unsupported buffer size.\n");
else
pa_log(__FILE__": AUDIO_SETINFO: %s\n", strerror(errno));
return -1;
}
return 0;
}
int pa__init(pa_core *c, pa_module*m) {
struct userdata *u = NULL;
const char *p;
int fd = -1;
int buffer_size;
int mode;
int record = 1, playback = 1;
pa_sample_spec ss;
pa_modargs *ma = NULL;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments.\n");
goto fail;
}
if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
pa_log(__FILE__": record= and playback= expect numeric argument.\n");
goto fail;
}
if (!playback && !record) {
pa_log(__FILE__": neither playback nor record enabled for device.\n");
goto fail;
}
mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0));
buffer_size = 16384;
if (pa_modargs_get_value_s32(ma, "buffer_size", &buffer_size) < 0) {
pa_log(__FILE__": failed to parse buffer size argument\n");
goto fail;
}
ss = c->default_sample_spec;
if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
pa_log(__FILE__": failed to parse sample specification\n");
goto fail;
}
if ((fd = open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), mode | O_NONBLOCK)) < 0)
goto fail;
pa_log_info(__FILE__": device opened in %s mode.\n", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR"));
if (pa_solaris_auto_format(fd, mode, &ss) < 0)
goto fail;
if ((mode != O_WRONLY) && (buffer_size >= 1))
if (pa_solaris_set_buffer(fd, buffer_size) < 0)
goto fail;
u = pa_xmalloc(sizeof(struct userdata));
u->core = c;
if (mode != O_WRONLY) {
u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
assert(u->source);
u->source->userdata = u;
u->source->get_latency = source_get_latency_cb;
pa_source_set_owner(u->source, m);
u->source->description = pa_sprintf_malloc("Solaris PCM on '%s'", p);
} else
u->source = NULL;
if (mode != O_RDONLY) {
u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
assert(u->sink);
u->sink->get_latency = sink_get_latency_cb;
u->sink->userdata = u;
pa_sink_set_owner(u->sink, m);
u->sink->description = pa_sprintf_malloc("Solaris PCM on '%s'", p);
} else
u->sink = NULL;
assert(u->source || u->sink);
u->io = pa_iochannel_new(c->mainloop, u->source ? fd : -1, u->sink ? fd : 0);
assert(u->io);
pa_iochannel_set_callback(u->io, io_callback, u);
u->fd = fd;
u->memchunk.memblock = NULL;
u->memchunk.length = 0;
u->sample_size = pa_frame_size(&ss);
u->buffer_size = buffer_size;
u->silence.memblock = pa_memblock_new(u->silence.length = CHUNK_SIZE, u->core->memblock_stat);
assert(u->silence.memblock);
pa_silence_memblock(u->silence.memblock, &ss);
u->silence.index = 0;
u->written_bytes = 0;
u->read_bytes = 0;
u->module = m;
m->userdata = u;
u->sig = pa_signal_new(SIGPOLL, sig_callback, u);
assert(u->sig);
ioctl(u->fd, I_SETSIG, S_MSG);
pa_modargs_free(ma);
return 0;
fail:
if (fd >= 0)
close(fd);
if (ma)
pa_modargs_free(ma);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u;
assert(c && m);
if (!(u = m->userdata))
return;
ioctl(u->fd, I_SETSIG, 0);
pa_signal_free(u->sig);
if (u->memchunk.memblock)
pa_memblock_unref(u->memchunk.memblock);
if (u->silence.memblock)
pa_memblock_unref(u->silence.memblock);
if (u->sink) {
pa_sink_disconnect(u->sink);
pa_sink_unref(u->sink);
}
if (u->source) {
pa_source_disconnect(u->source);
pa_source_unref(u->source);
}
pa_iochannel_free(u->io);
pa_xfree(u);
}

688
src/modules/module-tunnel.c Normal file
View file

@ -0,0 +1,688 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <polypcore/module.h>
#include <polypcore/util.h>
#include <polypcore/modargs.h>
#include <polypcore/log.h>
#include <polypcore/subscribe.h>
#include <polypcore/xmalloc.h>
#include <polypcore/sink-input.h>
#include <polypcore/pdispatch.h>
#include <polypcore/pstream.h>
#include <polypcore/pstream-util.h>
#include <polypcore/authkey.h>
#include <polypcore/socket-client.h>
#include <polypcore/socket-util.h>
#include <polypcore/authkey-prop.h>
#ifdef TUNNEL_SINK
#include "module-tunnel-sink-symdef.h"
PA_MODULE_DESCRIPTION("Tunnel module for sinks")
PA_MODULE_USAGE("server=<address> sink=<remote sink name> cookie=<filename> format=<sample format> channels=<number of channels> rate=<sample rate> sink_name=<name for the local sink>")
#else
#include "module-tunnel-source-symdef.h"
PA_MODULE_DESCRIPTION("Tunnel module for sources")
PA_MODULE_USAGE("server=<address> source=<remote source name> cookie=<filename> format=<sample format> channels=<number of channels> rate=<sample rate> source_name=<name for the local source>")
#endif
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_VERSION(PACKAGE_VERSION)
#define DEFAULT_SINK_NAME "tunnel"
#define DEFAULT_SOURCE_NAME "tunnel"
#define DEFAULT_TLENGTH (44100*2*2/10) //(10240*8)
#define DEFAULT_MAXLENGTH ((DEFAULT_TLENGTH*3)/2)
#define DEFAULT_MINREQ 512
#define DEFAULT_PREBUF (DEFAULT_TLENGTH-DEFAULT_MINREQ)
#define DEFAULT_FRAGSIZE 1024
#define DEFAULT_TIMEOUT 5
#define LATENCY_INTERVAL 10
static const char* const valid_modargs[] = {
"server",
"cookie",
"format",
"channels",
"rate",
#ifdef TUNNEL_SINK
"sink_name",
"sink",
#else
"source_name",
"source",
#endif
NULL,
};
static void command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
#ifdef TUNNEL_SINK
static void command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata);
#endif
static const pa_pdispatch_callback command_table[PA_COMMAND_MAX] = {
#ifdef TUNNEL_SINK
[PA_COMMAND_REQUEST] = command_request,
#endif
[PA_COMMAND_PLAYBACK_STREAM_KILLED] = command_stream_killed,
[PA_COMMAND_RECORD_STREAM_KILLED] = command_stream_killed
};
struct userdata {
pa_socket_client *client;
pa_pstream *pstream;
pa_pdispatch *pdispatch;
char *server_name;
#ifdef TUNNEL_SINK
char *sink_name;
pa_sink *sink;
uint32_t requested_bytes;
#else
char *source_name;
pa_source *source;
#endif
pa_module *module;
pa_core *core;
uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH];
uint32_t ctag;
uint32_t device_index;
uint32_t channel;
pa_usec_t host_latency;
pa_time_event *time_event;
int auth_cookie_in_property;
};
static void close_stuff(struct userdata *u) {
assert(u);
if (u->pstream) {
pa_pstream_close(u->pstream);
pa_pstream_unref(u->pstream);
u->pstream = NULL;
}
if (u->pdispatch) {
pa_pdispatch_unref(u->pdispatch);
u->pdispatch = NULL;
}
if (u->client) {
pa_socket_client_unref(u->client);
u->client = NULL;
}
#ifdef TUNNEL_SINK
if (u->sink) {
pa_sink_disconnect(u->sink);
pa_sink_unref(u->sink);
u->sink = NULL;
}
#else
if (u->source) {
pa_source_disconnect(u->source);
pa_source_unref(u->source);
u->source = NULL;
}
#endif
if (u->time_event) {
u->core->mainloop->time_free(u->time_event);
u->time_event = NULL;
}
}
static void die(struct userdata *u) {
assert(u);
close_stuff(u);
pa_module_unload_request(u->module);
}
static void command_stream_killed(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
struct userdata *u = userdata;
assert(pd && t && u && u->pdispatch == pd);
pa_log(__FILE__": stream killed\n");
die(u);
}
#ifdef TUNNEL_SINK
static void send_prebuf_request(struct userdata *u) {
pa_tagstruct *t;
t = pa_tagstruct_new(NULL, 0);
pa_tagstruct_putu32(t, PA_COMMAND_PREBUF_PLAYBACK_STREAM);
pa_tagstruct_putu32(t, u->ctag++);
pa_tagstruct_putu32(t, u->channel);
pa_pstream_send_tagstruct(u->pstream, t);
}
static void send_bytes(struct userdata *u) {
assert(u);
if (!u->pstream)
return;
while (u->requested_bytes > 0) {
pa_memchunk chunk;
if (pa_sink_render(u->sink, u->requested_bytes, &chunk) < 0) {
if (u->requested_bytes >= DEFAULT_TLENGTH-DEFAULT_PREBUF)
send_prebuf_request(u);
return;
}
pa_pstream_send_memblock(u->pstream, u->channel, 0, &chunk);
pa_memblock_unref(chunk.memblock);
if (chunk.length > u->requested_bytes)
u->requested_bytes = 0;
else
u->requested_bytes -= chunk.length;
}
}
static void command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
struct userdata *u = userdata;
uint32_t bytes, channel;
assert(pd && command == PA_COMMAND_REQUEST && t && u && u->pdispatch == pd);
if (pa_tagstruct_getu32(t, &channel) < 0 ||
pa_tagstruct_getu32(t, &bytes) < 0 ||
!pa_tagstruct_eof(t)) {
pa_log(__FILE__": invalid protocol reply\n");
die(u);
return;
}
if (channel != u->channel) {
pa_log(__FILE__": recieved data for invalid channel\n");
die(u);
return;
}
u->requested_bytes += bytes;
send_bytes(u);
}
#endif
static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
struct userdata *u = userdata;
pa_usec_t buffer_usec, sink_usec, source_usec, transport_usec;
int playing;
uint32_t queue_length;
uint64_t counter;
struct timeval local, remote, now;
assert(pd && u);
if (command != PA_COMMAND_REPLY) {
if (command == PA_COMMAND_ERROR)
pa_log(__FILE__": failed to get latency.\n");
else
pa_log(__FILE__": protocol error.\n");
die(u);
return;
}
if (pa_tagstruct_get_usec(t, &buffer_usec) < 0 ||
pa_tagstruct_get_usec(t, &sink_usec) < 0 ||
pa_tagstruct_get_usec(t, &source_usec) < 0 ||
pa_tagstruct_get_boolean(t, &playing) < 0 ||
pa_tagstruct_getu32(t, &queue_length) < 0 ||
pa_tagstruct_get_timeval(t, &local) < 0 ||
pa_tagstruct_get_timeval(t, &remote) < 0 ||
pa_tagstruct_getu64(t, &counter) < 0 ||
!pa_tagstruct_eof(t)) {
pa_log(__FILE__": invalid reply.\n");
die(u);
return;
}
pa_gettimeofday(&now);
if (pa_timeval_cmp(&local, &remote) < 0 && pa_timeval_cmp(&remote, &now)) {
/* local and remote seem to have synchronized clocks */
#ifdef TUNNEL_SINK
transport_usec = pa_timeval_diff(&remote, &local);
#else
transport_usec = pa_timeval_diff(&now, &remote);
#endif
} else
transport_usec = pa_timeval_diff(&now, &local)/2;
#ifdef TUNNEL_SINK
u->host_latency = sink_usec + transport_usec;
#else
u->host_latency = source_usec + transport_usec;
if (u->host_latency > sink_usec)
u->host_latency -= sink_usec;
else
u->host_latency = 0;
#endif
/* pa_log(__FILE__": estimated host latency: %0.0f usec\n", (double) u->host_latency); */
}
static void request_latency(struct userdata *u) {
pa_tagstruct *t;
struct timeval now;
uint32_t tag;
assert(u);
t = pa_tagstruct_new(NULL, 0);
#ifdef TUNNEL_SINK
pa_tagstruct_putu32(t, PA_COMMAND_GET_PLAYBACK_LATENCY);
#else
pa_tagstruct_putu32(t, PA_COMMAND_GET_RECORD_LATENCY);
#endif
pa_tagstruct_putu32(t, tag = u->ctag++);
pa_tagstruct_putu32(t, u->channel);
pa_gettimeofday(&now);
pa_tagstruct_put_timeval(t, &now);
pa_tagstruct_putu64(t, 0);
pa_pstream_send_tagstruct(u->pstream, t);
pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_latency_callback, u);
}
static void create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) {
struct userdata *u = userdata;
assert(pd && u && u->pdispatch == pd);
if (command != PA_COMMAND_REPLY) {
if (command == PA_COMMAND_ERROR)
pa_log(__FILE__": failed to create stream.\n");
else
pa_log(__FILE__": protocol error.\n");
die(u);
return;
}
if (pa_tagstruct_getu32(t, &u->channel) < 0 ||
pa_tagstruct_getu32(t, &u->device_index) < 0 ||
#ifdef TUNNEL_SINK
pa_tagstruct_getu32(t, &u->requested_bytes) < 0 ||
#endif
!pa_tagstruct_eof(t)) {
pa_log(__FILE__": invalid reply.\n");
die(u);
return;
}
request_latency(u);
#ifdef TUNNEL_SINK
send_bytes(u);
#endif
}
static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) {
struct userdata *u = userdata;
pa_tagstruct *reply;
char name[256], un[128], hn[128];
assert(pd && u && u->pdispatch == pd);
if (command != PA_COMMAND_REPLY || !pa_tagstruct_eof(t)) {
if (command == PA_COMMAND_ERROR)
pa_log(__FILE__": failed to authenticate\n");
else
pa_log(__FILE__": protocol error.\n");
die(u);
return;
}
#ifdef TUNNEL_SINK
snprintf(name, sizeof(name), "Tunnel from host '%s', user '%s', sink '%s'",
pa_get_host_name(hn, sizeof(hn)),
pa_get_user_name(un, sizeof(un)),
u->sink->name);
#else
snprintf(name, sizeof(name), "Tunnel from host '%s', user '%s', source '%s'",
pa_get_host_name(hn, sizeof(hn)),
pa_get_user_name(un, sizeof(un)),
u->source->name);
#endif
reply = pa_tagstruct_new(NULL, 0);
pa_tagstruct_putu32(reply, PA_COMMAND_SET_CLIENT_NAME);
pa_tagstruct_putu32(reply, tag = u->ctag++);
pa_tagstruct_puts(reply, name);
pa_pstream_send_tagstruct(u->pstream, reply);
/* We ignore the server's reply here */
reply = pa_tagstruct_new(NULL, 0);
#ifdef TUNNEL_SINK
pa_tagstruct_putu32(reply, PA_COMMAND_CREATE_PLAYBACK_STREAM);
pa_tagstruct_putu32(reply, tag = u->ctag++);
pa_tagstruct_puts(reply, name);
pa_tagstruct_put_sample_spec(reply, &u->sink->sample_spec);
pa_tagstruct_putu32(reply, PA_INVALID_INDEX);
pa_tagstruct_puts(reply, u->sink_name);
pa_tagstruct_putu32(reply, DEFAULT_MAXLENGTH);
pa_tagstruct_put_boolean(reply, 0);
pa_tagstruct_putu32(reply, DEFAULT_TLENGTH);
pa_tagstruct_putu32(reply, DEFAULT_PREBUF);
pa_tagstruct_putu32(reply, DEFAULT_MINREQ);
pa_tagstruct_putu32(reply, PA_VOLUME_NORM);
#else
pa_tagstruct_putu32(reply, PA_COMMAND_CREATE_RECORD_STREAM);
pa_tagstruct_putu32(reply, tag = u->ctag++);
pa_tagstruct_puts(reply, name);
pa_tagstruct_put_sample_spec(reply, &u->source->sample_spec);
pa_tagstruct_putu32(reply, PA_INVALID_INDEX);
pa_tagstruct_puts(reply, u->source_name);
pa_tagstruct_putu32(reply, DEFAULT_MAXLENGTH);
pa_tagstruct_put_boolean(reply, 0);
pa_tagstruct_putu32(reply, DEFAULT_FRAGSIZE);
#endif
pa_pstream_send_tagstruct(u->pstream, reply);
pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, create_stream_callback, u);
}
static void pstream_die_callback(pa_pstream *p, void *userdata) {
struct userdata *u = userdata;
assert(p && u);
pa_log(__FILE__": stream died.\n");
die(u);
}
static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, void *userdata) {
struct userdata *u = userdata;
assert(p && packet && u);
if (pa_pdispatch_run(u->pdispatch, packet, u) < 0) {
pa_log(__FILE__": invalid packet\n");
die(u);
}
}
#ifndef TUNNEL_SINK
static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, uint32_t delta, const pa_memchunk *chunk, void *userdata) {
struct userdata *u = userdata;
assert(p && chunk && u);
if (channel != u->channel) {
pa_log(__FILE__": recieved memory block on bad channel.\n");
die(u);
return;
}
pa_source_post(u->source, chunk);
}
#endif
static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) {
struct userdata *u = userdata;
pa_tagstruct *t;
uint32_t tag;
assert(sc && u && u->client == sc);
pa_socket_client_unref(u->client);
u->client = NULL;
if (!io) {
pa_log(__FILE__": connection failed.\n");
pa_module_unload_request(u->module);
return;
}
u->pstream = pa_pstream_new(u->core->mainloop, io, u->core->memblock_stat);
u->pdispatch = pa_pdispatch_new(u->core->mainloop, command_table, PA_COMMAND_MAX);
pa_pstream_set_die_callback(u->pstream, pstream_die_callback, u);
pa_pstream_set_recieve_packet_callback(u->pstream, pstream_packet_callback, u);
#ifndef TUNNEL_SINK
pa_pstream_set_recieve_memblock_callback(u->pstream, pstream_memblock_callback, u);
#endif
t = pa_tagstruct_new(NULL, 0);
pa_tagstruct_putu32(t, PA_COMMAND_AUTH);
pa_tagstruct_putu32(t, tag = u->ctag++);
pa_tagstruct_put_arbitrary(t, u->auth_cookie, sizeof(u->auth_cookie));
pa_pstream_send_tagstruct(u->pstream, t);
pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, u);
}
#ifdef TUNNEL_SINK
static void sink_notify(pa_sink*sink) {
struct userdata *u;
assert(sink && sink->userdata);
u = sink->userdata;
send_bytes(u);
}
static pa_usec_t sink_get_latency(pa_sink *sink) {
struct userdata *u;
uint32_t l;
pa_usec_t usec = 0;
assert(sink && sink->userdata);
u = sink->userdata;
l = DEFAULT_TLENGTH;
if (l > u->requested_bytes) {
l -= u->requested_bytes;
usec += pa_bytes_to_usec(l, &u->sink->sample_spec);
}
usec += u->host_latency;
return usec;
}
#else
static pa_usec_t source_get_latency(pa_source *source) {
struct userdata *u;
assert(source && source->userdata);
u = source->userdata;
return u->host_latency;
}
#endif
static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) {
struct userdata *u = userdata;
struct timeval ntv;
assert(m && e && u);
request_latency(u);
pa_gettimeofday(&ntv);
ntv.tv_sec += LATENCY_INTERVAL;
m->time_restart(e, &ntv);
}
static int load_key(struct userdata *u, const char*fn) {
assert(u);
u->auth_cookie_in_property = 0;
if (!fn && pa_authkey_prop_get(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) {
pa_log_debug(__FILE__": using already loaded auth cookie.\n");
pa_authkey_prop_ref(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
u->auth_cookie_in_property = 1;
return 0;
}
if (!fn)
fn = PA_NATIVE_COOKIE_FILE;
if (pa_authkey_load_auto(fn, u->auth_cookie, sizeof(u->auth_cookie)) < 0)
return -1;
pa_log_debug(__FILE__": loading cookie from disk.\n");
if (pa_authkey_prop_put(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0)
u->auth_cookie_in_property = 1;
return 0;
}
int pa__init(pa_core *c, pa_module*m) {
pa_modargs *ma = NULL;
struct userdata *u = NULL;
pa_sample_spec ss;
struct timeval ntv;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments\n");
goto fail;
}
u = pa_xmalloc(sizeof(struct userdata));
m->userdata = u;
u->module = m;
u->core = c;
u->client = NULL;
u->pdispatch = NULL;
u->pstream = NULL;
u->server_name = NULL;
#ifdef TUNNEL_SINK
u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));;
u->sink = NULL;
u->requested_bytes = 0;
#else
u->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL));;
u->source = NULL;
#endif
u->ctag = 1;
u->device_index = u->channel = PA_INVALID_INDEX;
u->host_latency = 0;
u->auth_cookie_in_property = 0;
u->time_event = NULL;
if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0)
goto fail;
if (!(u->server_name = pa_xstrdup(pa_modargs_get_value(ma, "server", NULL)))) {
pa_log(__FILE__": no server specified.\n");
goto fail;
}
ss = c->default_sample_spec;
if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
pa_log(__FILE__": invalid sample format specification\n");
goto fail;
}
if (!(u->client = pa_socket_client_new_string(c->mainloop, u->server_name, PA_NATIVE_DEFAULT_PORT))) {
pa_log(__FILE__": failed to connect to server '%s'\n", u->server_name);
goto fail;
}
if (!u->client)
goto fail;
pa_socket_client_set_callback(u->client, on_connection, u);
#ifdef TUNNEL_SINK
if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) {
pa_log(__FILE__": failed to create sink.\n");
goto fail;
}
u->sink->notify = sink_notify;
u->sink->get_latency = sink_get_latency;
u->sink->userdata = u;
u->sink->description = pa_sprintf_malloc("Tunnel to '%s%s%s'", u->sink_name ? u->sink_name : "", u->sink_name ? "@" : "", u->server_name);
pa_sink_set_owner(u->sink, m);
#else
if (!(u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL))) {
pa_log(__FILE__": failed to create source.\n");
goto fail;
}
u->source->get_latency = source_get_latency;
u->source->userdata = u;
u->source->description = pa_sprintf_malloc("Tunnel to '%s%s%s'", u->source_name ? u->source_name : "", u->source_name ? "@" : "", u->server_name);
pa_source_set_owner(u->source, m);
#endif
pa_gettimeofday(&ntv);
ntv.tv_sec += LATENCY_INTERVAL;
u->time_event = c->mainloop->time_new(c->mainloop, &ntv, timeout_callback, u);
pa_modargs_free(ma);
return 0;
fail:
pa__done(c, m);
if (ma)
pa_modargs_free(ma);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata* u;
assert(c && m);
if (!(u = m->userdata))
return;
close_stuff(u);
if (u->auth_cookie_in_property)
pa_authkey_prop_unref(c, PA_NATIVE_COOKIE_PROPERTY_NAME);
#ifdef TUNNEL_SINK
pa_xfree(u->sink_name);
#else
pa_xfree(u->source_name);
#endif
pa_xfree(u->server_name);
pa_xfree(u);
}

View file

@ -0,0 +1,583 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <windows.h>
#include <mmsystem.h>
#include <assert.h>
#include <polyp/mainloop-api.h>
#include <polypcore/sink.h>
#include <polypcore/source.h>
#include <polypcore/module.h>
#include <polypcore/modargs.h>
#include <polypcore/sample-util.h>
#include <polypcore/util.h>
#include <polypcore/log.h>
#include <polypcore/xmalloc.h>
#include "module-waveout-symdef.h"
PA_MODULE_AUTHOR("Pierre Ossman")
PA_MODULE_DESCRIPTION("Windows waveOut Sink/Source")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> fragments=<number of fragments> fragment_size=<fragment size>")
#define DEFAULT_SINK_NAME "wave_output"
#define DEFAULT_SOURCE_NAME "wave_input"
struct userdata {
pa_sink *sink;
pa_source *source;
pa_core *core;
pa_time_event *event;
pa_defer_event *defer;
pa_usec_t poll_timeout;
uint32_t fragments, fragment_size;
uint32_t free_ofrags, free_ifrags;
DWORD written_bytes;
int cur_ohdr, cur_ihdr;
unsigned int oremain;
WAVEHDR *ohdrs, *ihdrs;
pa_memchunk silence;
HWAVEOUT hwo;
HWAVEIN hwi;
pa_module *module;
CRITICAL_SECTION crit;
};
static const char* const valid_modargs[] = {
"sink_name",
"source_name",
"record",
"playback",
"fragments",
"fragment_size",
"format",
"rate",
"channels",
NULL
};
static void update_usage(struct userdata *u) {
pa_module_set_used(u->module,
(u->sink ? pa_idxset_size(u->sink->inputs) : 0) +
(u->sink ? pa_idxset_size(u->sink->monitor_source->outputs) : 0) +
(u->source ? pa_idxset_size(u->source->outputs) : 0));
}
static void do_write(struct userdata *u)
{
uint32_t free_frags, remain;
pa_memchunk memchunk, *cur_chunk;
WAVEHDR *hdr;
MMRESULT res;
if (!u->sink)
return;
EnterCriticalSection(&u->crit);
free_frags = u->free_ofrags;
u->free_ofrags = 0;
LeaveCriticalSection(&u->crit);
while (free_frags) {
hdr = &u->ohdrs[u->cur_ohdr];
if (hdr->dwFlags & WHDR_PREPARED)
waveOutUnprepareHeader(u->hwo, hdr, sizeof(WAVEHDR));
remain = u->oremain;
while (remain) {
cur_chunk = &memchunk;
if (pa_sink_render(u->sink, remain, cur_chunk) < 0) {
/*
* Don't fill with silence unless we're getting close to
* underflowing.
*/
if (free_frags > u->fragments/2)
cur_chunk = &u->silence;
else {
EnterCriticalSection(&u->crit);
u->free_ofrags += free_frags;
LeaveCriticalSection(&u->crit);
u->oremain = remain;
return;
}
}
assert(cur_chunk->memblock);
assert(cur_chunk->memblock->data);
assert(cur_chunk->length);
memcpy(hdr->lpData + u->fragment_size - remain,
(char*)cur_chunk->memblock->data + cur_chunk->index,
(cur_chunk->length < remain)?cur_chunk->length:remain);
remain -= (cur_chunk->length < remain)?cur_chunk->length:remain;
if (cur_chunk != &u->silence) {
pa_memblock_unref(cur_chunk->memblock);
cur_chunk->memblock = NULL;
}
}
res = waveOutPrepareHeader(u->hwo, hdr, sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR) {
pa_log_error(__FILE__ ": ERROR: Unable to prepare waveOut block: %d\n",
res);
}
res = waveOutWrite(u->hwo, hdr, sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR) {
pa_log_error(__FILE__ ": ERROR: Unable to write waveOut block: %d\n",
res);
}
u->written_bytes += u->fragment_size;
free_frags--;
u->cur_ohdr++;
u->cur_ohdr %= u->fragments;
u->oremain = u->fragment_size;
}
}
static void do_read(struct userdata *u)
{
uint32_t free_frags;
pa_memchunk memchunk;
WAVEHDR *hdr;
MMRESULT res;
if (!u->source)
return;
EnterCriticalSection(&u->crit);
free_frags = u->free_ifrags;
u->free_ifrags = 0;
LeaveCriticalSection(&u->crit);
while (free_frags) {
hdr = &u->ihdrs[u->cur_ihdr];
if (hdr->dwFlags & WHDR_PREPARED)
waveInUnprepareHeader(u->hwi, hdr, sizeof(WAVEHDR));
if (hdr->dwBytesRecorded) {
memchunk.memblock = pa_memblock_new(hdr->dwBytesRecorded, u->core->memblock_stat);
assert(memchunk.memblock);
memcpy((char*)memchunk.memblock->data, hdr->lpData, hdr->dwBytesRecorded);
memchunk.length = memchunk.memblock->length = hdr->dwBytesRecorded;
memchunk.index = 0;
pa_source_post(u->source, &memchunk);
pa_memblock_unref(memchunk.memblock);
}
res = waveInPrepareHeader(u->hwi, hdr, sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR) {
pa_log_error(__FILE__ ": ERROR: Unable to prepare waveIn block: %d\n",
res);
}
res = waveInAddBuffer(u->hwi, hdr, sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR) {
pa_log_error(__FILE__ ": ERROR: Unable to add waveIn block: %d\n",
res);
}
free_frags--;
u->cur_ihdr++;
u->cur_ihdr %= u->fragments;
}
}
static void poll_cb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) {
struct userdata *u = userdata;
struct timeval ntv;
assert(u);
update_usage(u);
do_write(u);
do_read(u);
pa_gettimeofday(&ntv);
pa_timeval_add(&ntv, u->poll_timeout);
a->time_restart(e, &ntv);
}
static void defer_cb(pa_mainloop_api*a, pa_defer_event *e, void *userdata) {
struct userdata *u = userdata;
assert(u);
a->defer_enable(e, 0);
do_write(u);
do_read(u);
}
static void CALLBACK chunk_done_cb(HWAVEOUT hwo, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) {
struct userdata *u = (struct userdata *)inst;
if (msg != WOM_DONE)
return;
EnterCriticalSection(&u->crit);
u->free_ofrags++;
assert(u->free_ofrags <= u->fragments);
LeaveCriticalSection(&u->crit);
}
static void CALLBACK chunk_ready_cb(HWAVEIN hwi, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) {
struct userdata *u = (struct userdata *)inst;
if (msg != WIM_DATA)
return;
EnterCriticalSection(&u->crit);
u->free_ifrags++;
assert(u->free_ifrags <= u->fragments);
LeaveCriticalSection(&u->crit);
}
static pa_usec_t sink_get_latency_cb(pa_sink *s) {
struct userdata *u = s->userdata;
uint32_t free_frags;
MMTIME mmt;
assert(s && u && u->sink);
memset(&mmt, 0, sizeof(mmt));
mmt.wType = TIME_BYTES;
if (waveOutGetPosition(u->hwo, &mmt, sizeof(mmt)) == MMSYSERR_NOERROR)
return pa_bytes_to_usec(u->written_bytes - mmt.u.cb, &s->sample_spec);
else {
EnterCriticalSection(&u->crit);
free_frags = u->free_ofrags;
LeaveCriticalSection(&u->crit);
return pa_bytes_to_usec((u->fragments - free_frags) * u->fragment_size,
&s->sample_spec);
}
}
static pa_usec_t source_get_latency_cb(pa_source *s) {
pa_usec_t r = 0;
struct userdata *u = s->userdata;
uint32_t free_frags;
assert(s && u && u->sink);
EnterCriticalSection(&u->crit);
free_frags = u->free_ifrags;
LeaveCriticalSection(&u->crit);
r += pa_bytes_to_usec((free_frags + 1) * u->fragment_size, &s->sample_spec);
fprintf(stderr, "Latency: %d us\n", (int)r);
return r;
}
static void notify_sink_cb(pa_sink *s) {
struct userdata *u = s->userdata;
assert(u);
u->core->mainloop->defer_enable(u->defer, 1);
}
static void notify_source_cb(pa_source *s) {
struct userdata *u = s->userdata;
assert(u);
u->core->mainloop->defer_enable(u->defer, 1);
}
static int ss_to_waveformat(pa_sample_spec *ss, LPWAVEFORMATEX wf) {
wf->wFormatTag = WAVE_FORMAT_PCM;
if (ss->channels > 2) {
pa_log_error(__FILE__": ERROR: More than two channels not supported.\n");
return -1;
}
wf->nChannels = ss->channels;
switch (ss->rate) {
case 8000:
case 11025:
case 22005:
case 44100:
break;
default:
pa_log_error(__FILE__": ERROR: Unsupported sample rate.\n");
return -1;
}
wf->nSamplesPerSec = ss->rate;
if (ss->format == PA_SAMPLE_U8)
wf->wBitsPerSample = 8;
else if (ss->format == PA_SAMPLE_S16NE)
wf->wBitsPerSample = 16;
else {
pa_log_error(__FILE__": ERROR: Unsupported sample format.\n");
return -1;
}
wf->nBlockAlign = wf->nChannels * wf->wBitsPerSample/8;
wf->nAvgBytesPerSec = wf->nSamplesPerSec * wf->nBlockAlign;
wf->cbSize = 0;
return 0;
}
int pa__init(pa_core *c, pa_module*m) {
struct userdata *u = NULL;
HWAVEOUT hwo = INVALID_HANDLE_VALUE;
HWAVEIN hwi = INVALID_HANDLE_VALUE;
WAVEFORMATEX wf;
int nfrags, frag_size;
int record = 1, playback = 1;
pa_sample_spec ss;
pa_modargs *ma = NULL;
unsigned int i;
struct timeval tv;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments.\n");
goto fail;
}
if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
pa_log(__FILE__": record= and playback= expect boolean argument.\n");
goto fail;
}
if (!playback && !record) {
pa_log(__FILE__": neither playback nor record enabled for device.\n");
goto fail;
}
nfrags = 20;
frag_size = 1024;
if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
pa_log(__FILE__": failed to parse fragments arguments\n");
goto fail;
}
ss = c->default_sample_spec;
if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
pa_log(__FILE__": failed to parse sample specification\n");
goto fail;
}
if (ss_to_waveformat(&ss, &wf) < 0)
goto fail;
u = pa_xmalloc(sizeof(struct userdata));
if (record) {
if (waveInOpen(&hwi, WAVE_MAPPER, &wf, (DWORD_PTR)chunk_ready_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
goto fail;
if (waveInStart(hwi) != MMSYSERR_NOERROR)
goto fail;
pa_log_debug(__FILE__": Opened waveIn subsystem.\n");
}
if (playback) {
if (waveOutOpen(&hwo, WAVE_MAPPER, &wf, (DWORD_PTR)chunk_done_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
goto fail;
pa_log_debug(__FILE__": Opened waveOut subsystem.\n");
}
InitializeCriticalSection(&u->crit);
if (hwi != INVALID_HANDLE_VALUE) {
u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
assert(u->source);
u->source->userdata = u;
u->source->notify = notify_source_cb;
u->source->get_latency = source_get_latency_cb;
pa_source_set_owner(u->source, m);
u->source->description = pa_sprintf_malloc("Windows waveIn PCM");
} else
u->source = NULL;
if (hwo != INVALID_HANDLE_VALUE) {
u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
assert(u->sink);
u->sink->notify = notify_sink_cb;
u->sink->get_latency = sink_get_latency_cb;
u->sink->userdata = u;
pa_sink_set_owner(u->sink, m);
u->sink->description = pa_sprintf_malloc("Windows waveOut PCM");
} else
u->sink = NULL;
assert(u->source || u->sink);
u->core = c;
u->hwi = hwi;
u->hwo = hwo;
u->fragments = nfrags;
u->free_ifrags = u->fragments;
u->free_ofrags = u->fragments;
u->fragment_size = frag_size - (frag_size % pa_frame_size(&ss));
u->written_bytes = 0;
u->oremain = u->fragment_size;
u->poll_timeout = pa_bytes_to_usec(u->fragments * u->fragment_size / 3, &ss);
pa_gettimeofday(&tv);
pa_timeval_add(&tv, u->poll_timeout);
u->event = c->mainloop->time_new(c->mainloop, &tv, poll_cb, u);
assert(u->event);
u->defer = c->mainloop->defer_new(c->mainloop, defer_cb, u);
assert(u->defer);
c->mainloop->defer_enable(u->defer, 0);
u->cur_ihdr = 0;
u->cur_ohdr = 0;
u->ihdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments);
assert(u->ihdrs);
u->ohdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments);
assert(u->ohdrs);
for (i = 0;i < u->fragments;i++) {
u->ihdrs[i].dwBufferLength = u->fragment_size;
u->ohdrs[i].dwBufferLength = u->fragment_size;
u->ihdrs[i].lpData = pa_xmalloc(u->fragment_size);
assert(u->ihdrs);
u->ohdrs[i].lpData = pa_xmalloc(u->fragment_size);
assert(u->ohdrs);
}
u->silence.length = u->fragment_size;
u->silence.memblock = pa_memblock_new(u->silence.length, u->core->memblock_stat);
assert(u->silence.memblock);
pa_silence_memblock(u->silence.memblock, &ss);
u->silence.index = 0;
u->module = m;
m->userdata = u;
pa_modargs_free(ma);
return 0;
fail:
if (hwi != INVALID_HANDLE_VALUE)
waveInClose(hwi);
if (hwo != INVALID_HANDLE_VALUE)
waveOutClose(hwo);
if (u)
pa_xfree(u);
if (ma)
pa_modargs_free(ma);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u;
unsigned int i;
assert(c && m);
if (!(u = m->userdata))
return;
if (u->event)
c->mainloop->time_free(u->event);
if (u->defer)
c->mainloop->defer_free(u->defer);
if (u->sink) {
pa_sink_disconnect(u->sink);
pa_sink_unref(u->sink);
}
if (u->source) {
pa_source_disconnect(u->source);
pa_source_unref(u->source);
}
if (u->hwi != INVALID_HANDLE_VALUE) {
waveInReset(u->hwi);
waveInClose(u->hwi);
}
if (u->hwo != INVALID_HANDLE_VALUE) {
waveOutReset(u->hwo);
waveOutClose(u->hwo);
}
for (i = 0;i < u->fragments;i++) {
pa_xfree(u->ihdrs[i].lpData);
pa_xfree(u->ohdrs[i].lpData);
}
pa_xfree(u->ihdrs);
pa_xfree(u->ohdrs);
DeleteCriticalSection(&u->crit);
pa_xfree(u);
}

View file

@ -0,0 +1,173 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <polypcore/iochannel.h>
#include <polypcore/sink.h>
#include <polypcore/scache.h>
#include <polypcore/modargs.h>
#include <polypcore/xmalloc.h>
#include <polypcore/namereg.h>
#include <polypcore/log.h>
#include <polypcore/x11wrap.h>
#include "module-x11-bell-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("X11 Bell interceptor")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("sink=<sink to connect to> sample=<sample name> display=<X11 display>")
struct userdata {
pa_core *core;
int xkb_event_base;
char *sink_name;
char *scache_item;
Display *display;
pa_x11_wrapper *x11_wrapper;
pa_x11_client *x11_client;
};
static const char* const valid_modargs[] = {
"sink",
"sample",
"display",
NULL
};
static int ring_bell(struct userdata *u, int percent) {
pa_sink *s;
pa_cvolume cv;
assert(u);
if (!(s = pa_namereg_get(u->core, u->sink_name, PA_NAMEREG_SINK, 1))) {
pa_log(__FILE__": Invalid sink: %s\n", u->sink_name);
return -1;
}
pa_scache_play_item(u->core, u->scache_item, s, pa_cvolume_set(&cv, PA_CHANNELS_MAX, percent*PA_VOLUME_NORM/100));
return 0;
}
static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) {
XkbBellNotifyEvent *bne;
struct userdata *u = userdata;
assert(w && e && u && u->x11_wrapper == w);
if (((XkbEvent*) e)->any.xkb_type != XkbBellNotify)
return 0;
bne = (XkbBellNotifyEvent*) e;
if (ring_bell(u, bne->percent) < 0) {
pa_log_info(__FILE__": Ringing bell failed, reverting to X11 device bell.\n");
XkbForceDeviceBell(pa_x11_wrapper_get_display(w), bne->device, bne->bell_class, bne->bell_id, bne->percent);
}
return 1;
}
int pa__init(pa_core *c, pa_module*m) {
struct userdata *u = NULL;
pa_modargs *ma = NULL;
int major, minor;
unsigned int auto_ctrls, auto_values;
assert(c && m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments\n");
goto fail;
}
m->userdata = u = pa_xmalloc(sizeof(struct userdata));
u->core = c;
u->scache_item = pa_xstrdup(pa_modargs_get_value(ma, "sample", "x11-bell"));
u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
u->x11_client = NULL;
if (!(u->x11_wrapper = pa_x11_wrapper_get(c, pa_modargs_get_value(ma, "display", NULL))))
goto fail;
u->display = pa_x11_wrapper_get_display(u->x11_wrapper);
major = XkbMajorVersion;
minor = XkbMinorVersion;
if (!XkbLibraryVersion(&major, &minor)) {
pa_log(__FILE__": XkbLibraryVersion() failed\n");
goto fail;
}
major = XkbMajorVersion;
minor = XkbMinorVersion;
if (!XkbQueryExtension(u->display, NULL, &u->xkb_event_base, NULL, &major, &minor)) {
pa_log(__FILE__": XkbQueryExtension() failed\n");
goto fail;
}
XkbSelectEvents(u->display, XkbUseCoreKbd, XkbBellNotifyMask, XkbBellNotifyMask);
auto_ctrls = auto_values = XkbAudibleBellMask;
XkbSetAutoResetControls(u->display, XkbAudibleBellMask, &auto_ctrls, &auto_values);
XkbChangeEnabledControls(u->display, XkbUseCoreKbd, XkbAudibleBellMask, 0);
u->x11_client = pa_x11_client_new(u->x11_wrapper, x11_event_callback, u);
pa_modargs_free(ma);
return 0;
fail:
if (ma)
pa_modargs_free(ma);
if (m->userdata)
pa__done(c, m);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata *u = m->userdata;
assert(c && m && u);
pa_xfree(u->scache_item);
pa_xfree(u->sink_name);
if (u->x11_client)
pa_x11_client_free(u->x11_client);
if (u->x11_wrapper)
pa_x11_wrapper_unref(u->x11_wrapper);
pa_xfree(u);
}

View file

@ -0,0 +1,192 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
polypaudio 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 Lesser General Public License
along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <polypcore/module.h>
#include <polypcore/sink.h>
#include <polypcore/scache.h>
#include <polypcore/modargs.h>
#include <polypcore/xmalloc.h>
#include <polypcore/namereg.h>
#include <polypcore/log.h>
#include <polypcore/x11wrap.h>
#include <polypcore/util.h>
#include <polypcore/native-common.h>
#include <polypcore/authkey-prop.h>
#include <polypcore/authkey.h>
#include <polypcore/x11prop.h>
#include <polypcore/strlist.h>
#include <polypcore/props.h>
#include "module-x11-publish-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("X11 Credential Publisher")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("display=<X11 display>")
static const char* const valid_modargs[] = {
"display",
"sink",
"source",
"cookie",
NULL
};
struct userdata {
pa_core *core;
pa_x11_wrapper *x11_wrapper;
Display *display;
char *id;
uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH];
int auth_cookie_in_property;
};
static int load_key(struct userdata *u, const char*fn) {
assert(u);
u->auth_cookie_in_property = 0;
if (!fn && pa_authkey_prop_get(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) {
pa_log_debug(__FILE__": using already loaded auth cookie.\n");
pa_authkey_prop_ref(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
u->auth_cookie_in_property = 1;
return 0;
}
if (!fn)
fn = PA_NATIVE_COOKIE_FILE;
if (pa_authkey_load_auto(fn, u->auth_cookie, sizeof(u->auth_cookie)) < 0)
return -1;
pa_log_debug(__FILE__": loading cookie from disk.\n");
if (pa_authkey_prop_put(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0)
u->auth_cookie_in_property = 1;
return 0;
}
int pa__init(pa_core *c, pa_module*m) {
struct userdata *u;
pa_modargs *ma = NULL;
char hn[256], un[128];
char hx[PA_NATIVE_COOKIE_LENGTH*2+1];
const char *t;
char *s;
pa_strlist *l;
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments\n");
goto fail;
}
m->userdata = u = pa_xmalloc(sizeof(struct userdata));
u->core = c;
u->id = NULL;
u->auth_cookie_in_property = 0;
if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0)
goto fail;
if (!(u->x11_wrapper = pa_x11_wrapper_get(c, pa_modargs_get_value(ma, "display", NULL))))
goto fail;
u->display = pa_x11_wrapper_get_display(u->x11_wrapper);
if (!(l = pa_property_get(c, PA_NATIVE_SERVER_PROPERTY_NAME)))
goto fail;
s = pa_strlist_tostring(l);
pa_x11_set_prop(u->display, "POLYP_SERVER", s);
pa_xfree(s);
if (!pa_get_fqdn(hn, sizeof(hn)) || !pa_get_user_name(un, sizeof(un)))
goto fail;
u->id = pa_sprintf_malloc("%s@%s/%u", un, hn, (unsigned) getpid());
pa_x11_set_prop(u->display, "POLYP_ID", u->id);
if ((t = pa_modargs_get_value(ma, "source", NULL)))
pa_x11_set_prop(u->display, "POLYP_SOURCE", t);
if ((t = pa_modargs_get_value(ma, "sink", NULL)))
pa_x11_set_prop(u->display, "POLYP_SINK", t);
pa_x11_set_prop(u->display, "POLYP_COOKIE", pa_hexstr(u->auth_cookie, sizeof(u->auth_cookie), hx, sizeof(hx)));
pa_modargs_free(ma);
return 0;
fail:
if (ma)
pa_modargs_free(ma);
pa__done(c, m);
return -1;
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata*u;
assert(c && m);
if (!(u = m->userdata))
return;
if (u->x11_wrapper) {
char t[256];
/* Yes, here is a race condition */
if (!pa_x11_get_prop(u->display, "POLYP_ID", t, sizeof(t)) || strcmp(t, u->id))
pa_log("WARNING: Polypaudio information vanished from X11!\n");
else {
pa_x11_del_prop(u->display, "POLYP_ID");
pa_x11_del_prop(u->display, "POLYP_SERVER");
pa_x11_del_prop(u->display, "POLYP_SINK");
pa_x11_del_prop(u->display, "POLYP_SOURCE");
pa_x11_del_prop(u->display, "POLYP_COOKIE");
XSync(u->display, False);
}
}
if (u->x11_wrapper)
pa_x11_wrapper_unref(u->x11_wrapper);
if (u->auth_cookie_in_property)
pa_authkey_prop_unref(c, PA_NATIVE_COOKIE_PROPERTY_NAME);
pa_xfree(u->id);
pa_xfree(u);
}

View file

@ -0,0 +1,508 @@
/* $Id$ */
/***
This file is part of polypaudio.
polypaudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
polypaudio 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 Lesser General Public
License along with polypaudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <polypcore/howl-wrap.h>
#include <polypcore/xmalloc.h>
#include <polypcore/autoload.h>
#include <polypcore/sink.h>
#include <polypcore/source.h>
#include <polypcore/native-common.h>
#include <polypcore/util.h>
#include <polypcore/log.h>
#include <polypcore/subscribe.h>
#include <polypcore/dynarray.h>
#include <polypcore/endianmacros.h>
#include <polypcore/modargs.h>
#include "module-zeroconf-publish-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering")
PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher")
PA_MODULE_VERSION(PACKAGE_VERSION)
PA_MODULE_USAGE("port=<IP port number>")
#define SERVICE_NAME_SINK "_polypaudio-sink._tcp"
#define SERVICE_NAME_SOURCE "_polypaudio-source._tcp"
#define SERVICE_NAME_SERVER "_polypaudio-server._tcp"
static const char* const valid_modargs[] = {
"port",
NULL
};
struct service {
sw_discovery_oid oid;
char *name;
int published; /* 0 -> not yet registered, 1 -> registered with data from real device, 2 -> registered with data from autoload device */
struct {
int valid;
pa_namereg_type type;
uint32_t index;
} loaded;
struct {
int valid;
pa_namereg_type type;
uint32_t index;
} autoload;
};
struct userdata {
pa_core *core;
pa_howl_wrapper *howl_wrapper;
pa_hashmap *services;
pa_dynarray *sink_dynarray, *source_dynarray, *autoload_dynarray;
pa_subscription *subscription;
uint16_t port;
sw_discovery_oid server_oid;
};
static sw_result publish_reply(sw_discovery discovery, sw_discovery_publish_status status, sw_discovery_oid oid, sw_opaque extra) {
return SW_OKAY;
}
static void get_service_data(struct userdata *u, struct service *s, pa_sample_spec *ret_ss, char **ret_description, pa_typeid_t *ret_typeid) {
assert(u && s && s->loaded.valid && ret_ss && ret_description && ret_typeid);
if (s->loaded.type == PA_NAMEREG_SINK) {
pa_sink *sink = pa_idxset_get_by_index(u->core->sinks, s->loaded.index);
assert(sink);
*ret_ss = sink->sample_spec;
*ret_description = sink->description;
*ret_typeid = sink->typeid;
} else if (s->loaded.type == PA_NAMEREG_SOURCE) {
pa_source *source = pa_idxset_get_by_index(u->core->sources, s->loaded.index);
assert(source);
*ret_ss = source->sample_spec;
*ret_description = source->description;
*ret_typeid = source->typeid;
} else
assert(0);
}
static void txt_record_server_data(pa_core *c, sw_text_record t) {
char s[256];
assert(c);
sw_text_record_add_key_and_string_value(t, "server-version", PACKAGE_NAME" "PACKAGE_VERSION);
sw_text_record_add_key_and_string_value(t, "user-name", pa_get_user_name(s, sizeof(s)));
sw_text_record_add_key_and_string_value(t, "fqdn", pa_get_fqdn(s, sizeof(s)));
snprintf(s, sizeof(s), "0x%08x", c->cookie);
sw_text_record_add_key_and_string_value(t, "cookie", s);
}
static int publish_service(struct userdata *u, struct service *s) {
char t[256];
char hn[256];
int r = -1;
sw_text_record txt;
int free_txt = 0;
assert(u && s);
if ((s->published == 1 && s->loaded.valid) ||
(s->published == 2 && s->autoload.valid && !s->loaded.valid))
return 0;
if (s->published) {
sw_discovery_cancel(pa_howl_wrapper_get_discovery(u->howl_wrapper), s->oid);
s->published = 0;
}
snprintf(t, sizeof(t), "Networked Audio Device %s on %s", s->name, pa_get_host_name(hn, sizeof(hn)));
if (sw_text_record_init(&txt) != SW_OKAY) {
pa_log(__FILE__": sw_text_record_init() failed\n");
goto finish;
}
free_txt = 1;
sw_text_record_add_key_and_string_value(txt, "device", s->name);
txt_record_server_data(u->core, txt);
if (s->loaded.valid) {
char z[64], *description;
pa_typeid_t typeid;
pa_sample_spec ss;
get_service_data(u, s, &ss, &description, &typeid);
snprintf(z, sizeof(z), "%u", ss.rate);
sw_text_record_add_key_and_string_value(txt, "rate", z);
snprintf(z, sizeof(z), "%u", ss.channels);
sw_text_record_add_key_and_string_value(txt, "channels", z);
sw_text_record_add_key_and_string_value(txt, "format", pa_sample_format_to_string(ss.format));
sw_text_record_add_key_and_string_value(txt, "description", description);
snprintf(z, sizeof(z), "0x%8x", typeid);
sw_text_record_add_key_and_string_value(txt, "typeid", z);
if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t,
s->loaded.type == PA_NAMEREG_SINK ? SERVICE_NAME_SINK : SERVICE_NAME_SOURCE,
NULL, NULL, u->port, sw_text_record_bytes(txt), sw_text_record_len(txt),
publish_reply, s, &s->oid) != SW_OKAY) {
pa_log(__FILE__": failed to register sink on zeroconf.\n");
goto finish;
}
s->published = 1;
} else if (s->autoload.valid) {
if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t,
s->autoload.type == PA_NAMEREG_SINK ? SERVICE_NAME_SINK : SERVICE_NAME_SOURCE,
NULL, NULL, u->port, sw_text_record_bytes(txt), sw_text_record_len(txt),
publish_reply, s, &s->oid) != SW_OKAY) {
pa_log(__FILE__": failed to register sink on zeroconf.\n");
goto finish;
}
s->published = 2;
}
r = 0;
finish:
if (!s->published) {
/* Remove this service */
pa_hashmap_remove(u->services, s->name);
pa_xfree(s->name);
pa_xfree(s);
}
if (free_txt)
sw_text_record_fina(txt);
return r;
}
struct service *get_service(struct userdata *u, const char *name) {
struct service *s;
if ((s = pa_hashmap_get(u->services, name)))
return s;
s = pa_xmalloc(sizeof(struct service));
s->published = 0;
s->name = pa_xstrdup(name);
s->loaded.valid = s->autoload.valid = 0;
pa_hashmap_put(u->services, s->name, s);
return s;
}
static int publish_sink(struct userdata *u, pa_sink *s) {
struct service *svc;
assert(u && s);
svc = get_service(u, s->name);
if (svc->loaded.valid)
return 0;
svc->loaded.valid = 1;
svc->loaded.type = PA_NAMEREG_SINK;
svc->loaded.index = s->index;
pa_dynarray_put(u->sink_dynarray, s->index, svc);
return publish_service(u, svc);
}
static int publish_source(struct userdata *u, pa_source *s) {
struct service *svc;
assert(u && s);
svc = get_service(u, s->name);
if (svc->loaded.valid)
return 0;
svc->loaded.valid = 1;
svc->loaded.type = PA_NAMEREG_SOURCE;
svc->loaded.index = s->index;
pa_dynarray_put(u->source_dynarray, s->index, svc);
return publish_service(u, svc);
}
static int publish_autoload(struct userdata *u, pa_autoload_entry *s) {
struct service *svc;
assert(u && s);
svc = get_service(u, s->name);
if (svc->autoload.valid)
return 0;
svc->autoload.valid = 1;
svc->autoload.type = s->type;
svc->autoload.index = s->index;
pa_dynarray_put(u->autoload_dynarray, s->index, svc);
return publish_service(u, svc);
}
static int remove_sink(struct userdata *u, uint32_t index) {
struct service *svc;
assert(u && index != PA_INVALID_INDEX);
if (!(svc = pa_dynarray_get(u->sink_dynarray, index)))
return 0;
if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SINK)
return 0;
svc->loaded.valid = 0;
pa_dynarray_put(u->sink_dynarray, index, NULL);
return publish_service(u, svc);
}
static int remove_source(struct userdata *u, uint32_t index) {
struct service *svc;
assert(u && index != PA_INVALID_INDEX);
if (!(svc = pa_dynarray_get(u->source_dynarray, index)))
return 0;
if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SOURCE)
return 0;
svc->loaded.valid = 0;
pa_dynarray_put(u->source_dynarray, index, NULL);
return publish_service(u, svc);
}
static int remove_autoload(struct userdata *u, uint32_t index) {
struct service *svc;
assert(u && index != PA_INVALID_INDEX);
if (!(svc = pa_dynarray_get(u->autoload_dynarray, index)))
return 0;
if (!svc->autoload.valid)
return 0;
svc->autoload.valid = 0;
pa_dynarray_put(u->autoload_dynarray, index, NULL);
return publish_service(u, svc);
}
static void subscribe_callback(pa_core *c, pa_subscription_event_type t, uint32_t index, void *userdata) {
struct userdata *u = userdata;
assert(u && c);
switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)
case PA_SUBSCRIPTION_EVENT_SINK: {
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
pa_sink *sink;
if ((sink = pa_idxset_get_by_index(c->sinks, index))) {
if (publish_sink(u, sink) < 0)
goto fail;
}
} else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
if (remove_sink(u, index) < 0)
goto fail;
}
break;
case PA_SUBSCRIPTION_EVENT_SOURCE:
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
pa_source *source;
if ((source = pa_idxset_get_by_index(c->sources, index))) {
if (publish_source(u, source) < 0)
goto fail;
}
} else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
if (remove_source(u, index) < 0)
goto fail;
}
break;
case PA_SUBSCRIPTION_EVENT_AUTOLOAD:
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
pa_autoload_entry *autoload;
if ((autoload = pa_idxset_get_by_index(c->autoload_idxset, index))) {
if (publish_autoload(u, autoload) < 0)
goto fail;
}
} else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
if (remove_autoload(u, index) < 0)
goto fail;
}
break;
}
return;
fail:
if (u->subscription) {
pa_subscription_free(u->subscription);
u->subscription = NULL;
}
}
int pa__init(pa_core *c, pa_module*m) {
struct userdata *u;
uint32_t index, port = PA_NATIVE_DEFAULT_PORT;
pa_sink *sink;
pa_source *source;
pa_autoload_entry *autoload;
pa_modargs *ma = NULL;
char t[256], hn[256];
int free_txt = 0;
sw_text_record txt;
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log(__FILE__": failed to parse module arguments.\n");
goto fail;
}
if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port == 0 || port >= 0xFFFF) {
pa_log(__FILE__": invalid port specified.\n");
goto fail;
}
m->userdata = u = pa_xmalloc(sizeof(struct userdata));
u->core = c;
u->port = (uint16_t) port;
if (!(u->howl_wrapper = pa_howl_wrapper_get(c)))
goto fail;
u->services = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
u->sink_dynarray = pa_dynarray_new();
u->source_dynarray = pa_dynarray_new();
u->autoload_dynarray = pa_dynarray_new();
u->subscription = pa_subscription_new(c,
PA_SUBSCRIPTION_MASK_SINK|
PA_SUBSCRIPTION_MASK_SOURCE|
PA_SUBSCRIPTION_MASK_AUTOLOAD, subscribe_callback, u);
for (sink = pa_idxset_first(c->sinks, &index); sink; sink = pa_idxset_next(c->sinks, &index))
if (publish_sink(u, sink) < 0)
goto fail;
for (source = pa_idxset_first(c->sources, &index); source; source = pa_idxset_next(c->sources, &index))
if (publish_source(u, source) < 0)
goto fail;
if (c->autoload_idxset)
for (autoload = pa_idxset_first(c->autoload_idxset, &index); autoload; autoload = pa_idxset_next(c->autoload_idxset, &index))
if (publish_autoload(u, autoload) < 0)
goto fail;
snprintf(t, sizeof(t), "Networked Audio Server on %s", pa_get_host_name(hn, sizeof(hn)));
if (sw_text_record_init(&txt) != SW_OKAY) {
pa_log(__FILE__": sw_text_record_init() failed\n");
goto fail;
}
free_txt = 1;
txt_record_server_data(u->core, txt);
if (sw_discovery_publish(pa_howl_wrapper_get_discovery(u->howl_wrapper), 0, t,
SERVICE_NAME_SERVER,
NULL, NULL, u->port, sw_text_record_bytes(txt), sw_text_record_len(txt),
publish_reply, u, &u->server_oid) != SW_OKAY) {
pa_log(__FILE__": failed to register server on zeroconf.\n");
goto fail;
}
sw_text_record_fina(txt);
pa_modargs_free(ma);
return 0;
fail:
pa__done(c, m);
if (ma)
pa_modargs_free(ma);
if (free_txt)
sw_text_record_fina(txt);
return -1;
}
static void service_free(void *p, void *userdata) {
struct service *s = p;
struct userdata *u = userdata;
assert(s && u);
sw_discovery_cancel(pa_howl_wrapper_get_discovery(u->howl_wrapper), s->oid);
pa_xfree(s->name);
pa_xfree(s);
}
void pa__done(pa_core *c, pa_module*m) {
struct userdata*u;
assert(c && m);
if (!(u = m->userdata))
return;
if (u->services)
pa_hashmap_free(u->services, service_free, u);
if (u->sink_dynarray)
pa_dynarray_free(u->sink_dynarray, NULL, NULL);
if (u->source_dynarray)
pa_dynarray_free(u->source_dynarray, NULL, NULL);
if (u->autoload_dynarray)
pa_dynarray_free(u->autoload_dynarray, NULL, NULL);
if (u->subscription)
pa_subscription_free(u->subscription);
if (u->howl_wrapper)
pa_howl_wrapper_unref(u->howl_wrapper);
pa_xfree(u);
}