mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-11-07 13:30:03 -05:00
merge 'lennart' branch back into trunk.
git-svn-id: file:///home/lennart/svn/public/pulseaudio/trunk@1971 fefdeb5f-60dc-0310-8127-8f9354f1896f
This commit is contained in:
parent
6687dd0131
commit
a67c21f093
294 changed files with 79057 additions and 11614 deletions
|
|
@ -33,6 +33,7 @@
|
|||
#include <pulse/xmalloc.h>
|
||||
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/macro.h>
|
||||
|
||||
#include "alsa-util.h"
|
||||
|
||||
|
|
@ -42,7 +43,6 @@ struct pa_alsa_fdlist {
|
|||
/* This is a temporary buffer used to avoid lots of mallocs */
|
||||
struct pollfd *work_fds;
|
||||
|
||||
snd_pcm_t *pcm;
|
||||
snd_mixer_t *mixer;
|
||||
|
||||
pa_mainloop_api *m;
|
||||
|
|
@ -56,11 +56,16 @@ struct pa_alsa_fdlist {
|
|||
};
|
||||
|
||||
static void io_cb(pa_mainloop_api*a, pa_io_event* e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void *userdata) {
|
||||
struct pa_alsa_fdlist *fdl = (struct pa_alsa_fdlist*)userdata;
|
||||
|
||||
struct pa_alsa_fdlist *fdl = userdata;
|
||||
int err, i;
|
||||
unsigned short revents;
|
||||
|
||||
assert(a && fdl && (fdl->pcm || fdl->mixer) && fdl->fds && fdl->work_fds);
|
||||
pa_assert(a);
|
||||
pa_assert(fdl);
|
||||
pa_assert(fdl->mixer);
|
||||
pa_assert(fdl->fds);
|
||||
pa_assert(fdl->work_fds);
|
||||
|
||||
if (fdl->polled)
|
||||
return;
|
||||
|
|
@ -69,7 +74,7 @@ static void io_cb(pa_mainloop_api*a, pa_io_event* e, PA_GCC_UNUSED int fd, pa_io
|
|||
|
||||
memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds);
|
||||
|
||||
for (i = 0;i < fdl->num_fds;i++) {
|
||||
for (i = 0;i < fdl->num_fds; i++) {
|
||||
if (e == fdl->ios[i]) {
|
||||
if (events & PA_IO_EVENT_INPUT)
|
||||
fdl->work_fds[i].revents |= POLLIN;
|
||||
|
|
@ -83,63 +88,46 @@ static void io_cb(pa_mainloop_api*a, pa_io_event* e, PA_GCC_UNUSED int fd, pa_io
|
|||
}
|
||||
}
|
||||
|
||||
assert(i != fdl->num_fds);
|
||||
pa_assert(i != fdl->num_fds);
|
||||
|
||||
if (fdl->pcm)
|
||||
err = snd_pcm_poll_descriptors_revents(fdl->pcm, fdl->work_fds, fdl->num_fds, &revents);
|
||||
else
|
||||
err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents);
|
||||
|
||||
if (err < 0) {
|
||||
pa_log_error("Unable to get poll revent: %s",
|
||||
snd_strerror(err));
|
||||
if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) {
|
||||
pa_log_error("Unable to get poll revent: %s", snd_strerror(err));
|
||||
return;
|
||||
}
|
||||
|
||||
a->defer_enable(fdl->defer, 1);
|
||||
|
||||
if (revents) {
|
||||
if (fdl->pcm)
|
||||
fdl->cb(fdl->userdata);
|
||||
else
|
||||
snd_mixer_handle_events(fdl->mixer);
|
||||
}
|
||||
if (revents)
|
||||
snd_mixer_handle_events(fdl->mixer);
|
||||
}
|
||||
|
||||
static void defer_cb(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event* e, void *userdata) {
|
||||
struct pa_alsa_fdlist *fdl = (struct pa_alsa_fdlist*)userdata;
|
||||
struct pa_alsa_fdlist *fdl = userdata;
|
||||
int num_fds, i, err;
|
||||
struct pollfd *temp;
|
||||
|
||||
assert(a && fdl && (fdl->pcm || fdl->mixer));
|
||||
pa_assert(a);
|
||||
pa_assert(fdl);
|
||||
pa_assert(fdl->mixer);
|
||||
|
||||
a->defer_enable(fdl->defer, 0);
|
||||
|
||||
if (fdl->pcm)
|
||||
num_fds = snd_pcm_poll_descriptors_count(fdl->pcm);
|
||||
else
|
||||
num_fds = snd_mixer_poll_descriptors_count(fdl->mixer);
|
||||
assert(num_fds > 0);
|
||||
num_fds = snd_mixer_poll_descriptors_count(fdl->mixer);
|
||||
pa_assert(num_fds > 0);
|
||||
|
||||
if (num_fds != fdl->num_fds) {
|
||||
if (fdl->fds)
|
||||
pa_xfree(fdl->fds);
|
||||
if (fdl->work_fds)
|
||||
pa_xfree(fdl->work_fds);
|
||||
fdl->fds = pa_xmalloc0(sizeof(struct pollfd) * num_fds);
|
||||
fdl->work_fds = pa_xmalloc(sizeof(struct pollfd) * num_fds);
|
||||
fdl->fds = pa_xnew0(struct pollfd, num_fds);
|
||||
fdl->work_fds = pa_xnew(struct pollfd, num_fds);
|
||||
}
|
||||
|
||||
memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds);
|
||||
|
||||
if (fdl->pcm)
|
||||
err = snd_pcm_poll_descriptors(fdl->pcm, fdl->work_fds, num_fds);
|
||||
else
|
||||
err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds);
|
||||
|
||||
if (err < 0) {
|
||||
pa_log_error("Unable to get poll descriptors: %s",
|
||||
snd_strerror(err));
|
||||
if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) {
|
||||
pa_log_error("Unable to get poll descriptors: %s", snd_strerror(err));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -149,18 +137,18 @@ static void defer_cb(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event* e, void *u
|
|||
return;
|
||||
|
||||
if (fdl->ios) {
|
||||
for (i = 0;i < fdl->num_fds;i++)
|
||||
for (i = 0; i < fdl->num_fds; i++)
|
||||
a->io_free(fdl->ios[i]);
|
||||
|
||||
if (num_fds != fdl->num_fds) {
|
||||
pa_xfree(fdl->ios);
|
||||
fdl->ios = pa_xmalloc(sizeof(pa_io_event*) * num_fds);
|
||||
assert(fdl->ios);
|
||||
fdl->ios = NULL;
|
||||
}
|
||||
} else {
|
||||
fdl->ios = pa_xmalloc(sizeof(pa_io_event*) * num_fds);
|
||||
assert(fdl->ios);
|
||||
}
|
||||
|
||||
if (!fdl->ios)
|
||||
fdl->ios = pa_xnew(pa_io_event*, num_fds);
|
||||
|
||||
/* Swap pointers */
|
||||
temp = fdl->work_fds;
|
||||
fdl->work_fds = fdl->fds;
|
||||
|
|
@ -168,47 +156,41 @@ static void defer_cb(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event* e, void *u
|
|||
|
||||
fdl->num_fds = num_fds;
|
||||
|
||||
for (i = 0;i < num_fds;i++) {
|
||||
for (i = 0;i < num_fds;i++)
|
||||
fdl->ios[i] = a->io_new(a, fdl->fds[i].fd,
|
||||
((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) |
|
||||
((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0),
|
||||
io_cb, fdl);
|
||||
assert(fdl->ios[i]);
|
||||
}
|
||||
}
|
||||
|
||||
struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) {
|
||||
struct pa_alsa_fdlist *fdl;
|
||||
|
||||
fdl = pa_xmalloc(sizeof(struct pa_alsa_fdlist));
|
||||
fdl = pa_xnew0(struct pa_alsa_fdlist, 1);
|
||||
|
||||
fdl->num_fds = 0;
|
||||
fdl->fds = NULL;
|
||||
fdl->work_fds = NULL;
|
||||
|
||||
fdl->pcm = NULL;
|
||||
fdl->mixer = NULL;
|
||||
|
||||
fdl->m = NULL;
|
||||
fdl->defer = NULL;
|
||||
fdl->ios = NULL;
|
||||
|
||||
fdl->polled = 0;
|
||||
|
||||
return fdl;
|
||||
}
|
||||
|
||||
void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) {
|
||||
assert(fdl);
|
||||
pa_assert(fdl);
|
||||
|
||||
if (fdl->defer) {
|
||||
assert(fdl->m);
|
||||
pa_assert(fdl->m);
|
||||
fdl->m->defer_free(fdl->defer);
|
||||
}
|
||||
|
||||
if (fdl->ios) {
|
||||
int i;
|
||||
assert(fdl->m);
|
||||
pa_assert(fdl->m);
|
||||
for (i = 0;i < fdl->num_fds;i++)
|
||||
fdl->m->io_free(fdl->ios[i]);
|
||||
pa_xfree(fdl->ios);
|
||||
|
|
@ -222,29 +204,15 @@ void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) {
|
|||
pa_xfree(fdl);
|
||||
}
|
||||
|
||||
int pa_alsa_fdlist_init_pcm(struct pa_alsa_fdlist *fdl, snd_pcm_t *pcm_handle, pa_mainloop_api* m, void (*cb)(void *userdata), void *userdata) {
|
||||
assert(fdl && pcm_handle && m && !fdl->m && cb);
|
||||
|
||||
fdl->pcm = pcm_handle;
|
||||
fdl->m = m;
|
||||
|
||||
fdl->defer = m->defer_new(m, defer_cb, fdl);
|
||||
assert(fdl->defer);
|
||||
|
||||
fdl->cb = cb;
|
||||
fdl->userdata = userdata;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_alsa_fdlist_init_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m) {
|
||||
assert(fdl && mixer_handle && m && !fdl->m);
|
||||
int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m) {
|
||||
pa_assert(fdl);
|
||||
pa_assert(mixer_handle);
|
||||
pa_assert(m);
|
||||
pa_assert(!fdl->m);
|
||||
|
||||
fdl->mixer = mixer_handle;
|
||||
fdl->m = m;
|
||||
|
||||
fdl->defer = m->defer_new(m, defer_cb, fdl);
|
||||
assert(fdl->defer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -274,8 +242,8 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s
|
|||
|
||||
int i, ret;
|
||||
|
||||
assert(pcm_handle);
|
||||
assert(f);
|
||||
pa_assert(pcm_handle);
|
||||
pa_assert(f);
|
||||
|
||||
if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)
|
||||
return ret;
|
||||
|
|
@ -308,7 +276,7 @@ try_auto:
|
|||
|
||||
/* Set the hardware parameters of the given ALSA device. Returns the
|
||||
* selected fragment settings in *period and *period_size */
|
||||
int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *periods, snd_pcm_uframes_t *period_size) {
|
||||
int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *periods, snd_pcm_uframes_t *period_size, int *use_mmap) {
|
||||
int ret = -1;
|
||||
snd_pcm_uframes_t buffer_size;
|
||||
unsigned int r = ss->rate;
|
||||
|
|
@ -316,17 +284,32 @@ int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *p
|
|||
pa_sample_format_t f = ss->format;
|
||||
snd_pcm_hw_params_t *hwparams;
|
||||
|
||||
assert(pcm_handle);
|
||||
assert(ss);
|
||||
assert(periods);
|
||||
assert(period_size);
|
||||
pa_assert(pcm_handle);
|
||||
pa_assert(ss);
|
||||
pa_assert(periods);
|
||||
pa_assert(period_size);
|
||||
|
||||
snd_pcm_hw_params_alloca(&hwparams);
|
||||
|
||||
buffer_size = *periods * *period_size;
|
||||
|
||||
if ((ret = snd_pcm_hw_params_malloc(&hwparams)) < 0 ||
|
||||
(ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0 ||
|
||||
(ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0 ||
|
||||
(ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
|
||||
if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0 ||
|
||||
(ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0)
|
||||
goto finish;
|
||||
|
||||
if (use_mmap && *use_mmap) {
|
||||
if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) {
|
||||
|
||||
/* mmap() didn't work, fall back to interleaved */
|
||||
|
||||
if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
|
||||
goto finish;
|
||||
|
||||
if (use_mmap)
|
||||
*use_mmap = 0;
|
||||
}
|
||||
|
||||
} else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
|
||||
goto finish;
|
||||
|
||||
if ((ret = set_format(pcm_handle, hwparams, &f)) < 0)
|
||||
|
|
@ -346,7 +329,7 @@ int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *p
|
|||
goto finish;
|
||||
|
||||
if (ss->rate != r) {
|
||||
pa_log_warn("device doesn't support %u Hz, changed to %u Hz.", ss->rate, r);
|
||||
pa_log_warn("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, r);
|
||||
|
||||
/* If the sample rate deviates too much, we need to resample */
|
||||
if (r < ss->rate*.95 || r > ss->rate*1.05)
|
||||
|
|
@ -354,12 +337,12 @@ int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *p
|
|||
}
|
||||
|
||||
if (ss->channels != c) {
|
||||
pa_log_warn("device doesn't support %u channels, changed to %u.", ss->channels, c);
|
||||
pa_log_warn("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, c);
|
||||
ss->channels = c;
|
||||
}
|
||||
|
||||
if (ss->format != f) {
|
||||
pa_log_warn("device doesn't support sample format %s, changed to %s.", pa_sample_format_to_string(ss->format), pa_sample_format_to_string(f));
|
||||
pa_log_warn("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(f));
|
||||
ss->format = f;
|
||||
}
|
||||
|
||||
|
|
@ -370,24 +353,54 @@ int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *p
|
|||
(ret = snd_pcm_hw_params_get_period_size(hwparams, period_size, NULL)) < 0)
|
||||
goto finish;
|
||||
|
||||
assert(buffer_size > 0);
|
||||
assert(*period_size > 0);
|
||||
pa_assert(buffer_size > 0);
|
||||
pa_assert(*period_size > 0);
|
||||
*periods = buffer_size / *period_size;
|
||||
assert(*periods > 0);
|
||||
pa_assert(*periods > 0);
|
||||
|
||||
ret = 0;
|
||||
|
||||
finish:
|
||||
if (hwparams)
|
||||
snd_pcm_hw_params_free(hwparams);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int pa_alsa_set_sw_params(snd_pcm_t *pcm) {
|
||||
snd_pcm_sw_params_t *swparams;
|
||||
int err;
|
||||
|
||||
pa_assert(pcm);
|
||||
|
||||
snd_pcm_sw_params_alloca(&swparams);
|
||||
|
||||
if ((err = snd_pcm_sw_params_current(pcm, swparams) < 0)) {
|
||||
pa_log_warn("Unable to determine current swparams: %s\n", snd_strerror(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_sw_params_set_stop_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) {
|
||||
pa_log_warn("Unable to set stop threshold: %s\n", snd_strerror(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_sw_params_set_start_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) {
|
||||
pa_log_warn("Unable to set start threshold: %s\n", snd_strerror(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
if ((err = snd_pcm_sw_params(pcm, swparams)) < 0) {
|
||||
pa_log_warn("Unable to set sw params: %s\n", snd_strerror(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) {
|
||||
int err;
|
||||
|
||||
assert(mixer && dev);
|
||||
pa_assert(mixer);
|
||||
pa_assert(dev);
|
||||
|
||||
if ((err = snd_mixer_attach(mixer, dev)) < 0) {
|
||||
pa_log_warn("Unable to attach to mixer %s: %s", dev, snd_strerror(err));
|
||||
|
|
@ -410,10 +423,11 @@ int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) {
|
|||
snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback) {
|
||||
snd_mixer_elem_t *elem;
|
||||
snd_mixer_selem_id_t *sid = NULL;
|
||||
|
||||
snd_mixer_selem_id_alloca(&sid);
|
||||
|
||||
assert(mixer);
|
||||
assert(name);
|
||||
pa_assert(mixer);
|
||||
pa_assert(name);
|
||||
|
||||
snd_mixer_selem_id_set_name(sid, name);
|
||||
|
||||
|
|
|
|||
|
|
@ -32,15 +32,14 @@
|
|||
|
||||
#include <pulse/channelmap.h>
|
||||
|
||||
struct pa_alsa_fdlist;
|
||||
typedef struct pa_alsa_fdlist pa_alsa_fdlist;
|
||||
|
||||
struct pa_alsa_fdlist *pa_alsa_fdlist_new(void);
|
||||
void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl);
|
||||
int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m);
|
||||
|
||||
int pa_alsa_fdlist_init_pcm(struct pa_alsa_fdlist *fdl, snd_pcm_t *pcm_handle, pa_mainloop_api* m, void (*cb)(void *userdata), void *userdata);
|
||||
int pa_alsa_fdlist_init_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m);
|
||||
|
||||
int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *periods, snd_pcm_uframes_t *period_size);
|
||||
int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *periods, snd_pcm_uframes_t *period_size, int *use_mmap);
|
||||
int pa_alsa_set_sw_params(snd_pcm_t *pcm);
|
||||
|
||||
int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev);
|
||||
snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback);
|
||||
|
|
|
|||
|
|
@ -26,25 +26,25 @@
|
|||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/props.h>
|
||||
#include <pulse/xmalloc.h>
|
||||
#include <pulse/timeval.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/props.h>
|
||||
|
||||
#include "dbus-util.h"
|
||||
|
||||
struct pa_dbus_connection {
|
||||
int refcount;
|
||||
PA_REFCNT_DECLARE;
|
||||
|
||||
pa_core *core;
|
||||
DBusConnection *connection;
|
||||
const char *property_name;
|
||||
pa_defer_event* dispatch_event;
|
||||
};
|
||||
|
||||
static void dispatch_cb(pa_mainloop_api *ea, pa_defer_event *ev, void *userdata)
|
||||
{
|
||||
DBusConnection *conn = (DBusConnection *) userdata;
|
||||
static void dispatch_cb(pa_mainloop_api *ea, pa_defer_event *ev, void *userdata) {
|
||||
DBusConnection *conn = userdata;
|
||||
|
||||
if (dbus_connection_dispatch(conn) == DBUS_DISPATCH_COMPLETE) {
|
||||
/* no more data to process, disable the deferred */
|
||||
ea->defer_enable(ev, 0);
|
||||
|
|
@ -52,14 +52,17 @@ static void dispatch_cb(pa_mainloop_api *ea, pa_defer_event *ev, void *userdata)
|
|||
}
|
||||
|
||||
/* DBusDispatchStatusFunction callback for the pa mainloop */
|
||||
static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status,
|
||||
void *userdata)
|
||||
{
|
||||
pa_dbus_connection *c = (pa_dbus_connection*) userdata;
|
||||
static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata) {
|
||||
pa_dbus_connection *c = userdata;
|
||||
|
||||
pa_assert(c);
|
||||
|
||||
switch(status) {
|
||||
|
||||
case DBUS_DISPATCH_COMPLETE:
|
||||
c->core->mainloop->defer_enable(c->dispatch_event, 0);
|
||||
break;
|
||||
|
||||
case DBUS_DISPATCH_DATA_REMAINS:
|
||||
case DBUS_DISPATCH_NEED_MEMORY:
|
||||
default:
|
||||
|
|
@ -68,11 +71,13 @@ static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status,
|
|||
}
|
||||
}
|
||||
|
||||
static pa_io_event_flags_t
|
||||
get_watch_flags(DBusWatch *watch)
|
||||
{
|
||||
unsigned int flags = dbus_watch_get_flags(watch);
|
||||
pa_io_event_flags_t events = PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR;
|
||||
static pa_io_event_flags_t get_watch_flags(DBusWatch *watch) {
|
||||
unsigned int flags;
|
||||
pa_io_event_flags_t events = 0;
|
||||
|
||||
pa_assert(watch);
|
||||
|
||||
flags = dbus_watch_get_flags(watch);
|
||||
|
||||
/* no watch flags for disabled watches */
|
||||
if (!dbus_watch_get_enabled(watch))
|
||||
|
|
@ -83,21 +88,22 @@ get_watch_flags(DBusWatch *watch)
|
|||
if (flags & DBUS_WATCH_WRITABLE)
|
||||
events |= PA_IO_EVENT_OUTPUT;
|
||||
|
||||
return events;
|
||||
return events | PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR;
|
||||
}
|
||||
|
||||
/* pa_io_event_cb_t IO event handler */
|
||||
static void handle_io_event(PA_GCC_UNUSED pa_mainloop_api *ea, pa_io_event *e,
|
||||
int fd, pa_io_event_flags_t events, void *userdata)
|
||||
{
|
||||
static void handle_io_event(PA_GCC_UNUSED pa_mainloop_api *ea, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
|
||||
unsigned int flags = 0;
|
||||
DBusWatch *watch = (DBusWatch*) userdata;
|
||||
DBusWatch *watch = userdata;
|
||||
|
||||
assert(fd == dbus_watch_get_fd(watch));
|
||||
#if HAVE_DBUS_WATCH_GET_UNIX_FD
|
||||
pa_assert(fd == dbus_watch_get_unix_fd(watch));
|
||||
#else
|
||||
pa_assert(fd == dbus_watch_get_fd(watch));
|
||||
#endif
|
||||
|
||||
if (!dbus_watch_get_enabled(watch)) {
|
||||
pa_log_warn("Asked to handle disabled watch: %p %i",
|
||||
(void *) watch, fd);
|
||||
pa_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -114,10 +120,8 @@ static void handle_io_event(PA_GCC_UNUSED pa_mainloop_api *ea, pa_io_event *e,
|
|||
}
|
||||
|
||||
/* pa_time_event_cb_t timer event handler */
|
||||
static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e,
|
||||
const struct timeval *tv, void *userdata)
|
||||
{
|
||||
DBusTimeout *timeout = (DBusTimeout*) userdata;
|
||||
static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e, const struct timeval *tv, void *userdata) {
|
||||
DBusTimeout *timeout = userdata;
|
||||
|
||||
if (dbus_timeout_get_enabled(timeout)) {
|
||||
struct timeval next = *tv;
|
||||
|
|
@ -130,103 +134,154 @@ static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e,
|
|||
}
|
||||
|
||||
/* DBusAddWatchFunction callback for pa mainloop */
|
||||
static dbus_bool_t add_watch(DBusWatch *watch, void *data)
|
||||
{
|
||||
static dbus_bool_t add_watch(DBusWatch *watch, void *data) {
|
||||
pa_core *c = PA_CORE(data);
|
||||
pa_io_event *ev;
|
||||
pa_core *c = (pa_core*) data;
|
||||
|
||||
ev = c->mainloop->io_new(c->mainloop, dbus_watch_get_fd(watch),
|
||||
get_watch_flags(watch),
|
||||
handle_io_event, (void*) watch);
|
||||
if (NULL == ev)
|
||||
return FALSE;
|
||||
pa_assert(watch);
|
||||
pa_assert(c);
|
||||
|
||||
/* dbus_watch_set_data(watch, (void*) ev, c->mainloop->io_free); */
|
||||
dbus_watch_set_data(watch, (void*) ev, NULL);
|
||||
ev = c->mainloop->io_new(
|
||||
c->mainloop,
|
||||
#if HAVE_DBUS_WATCH_GET_UNIX_FD
|
||||
dbus_watch_get_unix_fd(watch),
|
||||
#else
|
||||
dbus_watch_get_fd(watch),
|
||||
#endif
|
||||
get_watch_flags(watch), handle_io_event, watch);
|
||||
|
||||
dbus_watch_set_data(watch, ev, NULL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* DBusRemoveWatchFunction callback for pa mainloop */
|
||||
static void remove_watch(DBusWatch *watch, void *data)
|
||||
{
|
||||
pa_core *c = (pa_core*) data;
|
||||
pa_io_event *ev = (pa_io_event*) dbus_watch_get_data(watch);
|
||||
static void remove_watch(DBusWatch *watch, void *data) {
|
||||
pa_core *c = PA_CORE(data);
|
||||
pa_io_event *ev;
|
||||
|
||||
/* free the event */
|
||||
if (NULL != ev)
|
||||
pa_assert(watch);
|
||||
pa_assert(c);
|
||||
|
||||
if ((ev = dbus_watch_get_data(watch)))
|
||||
c->mainloop->io_free(ev);
|
||||
}
|
||||
|
||||
/* DBusWatchToggledFunction callback for pa mainloop */
|
||||
static void toggle_watch(DBusWatch *watch, void *data)
|
||||
{
|
||||
pa_core *c = (pa_core*) data;
|
||||
pa_io_event *ev = (pa_io_event*) dbus_watch_get_data(watch);
|
||||
static void toggle_watch(DBusWatch *watch, void *data) {
|
||||
pa_core *c = PA_CORE(data);
|
||||
pa_io_event *ev;
|
||||
|
||||
pa_assert(watch);
|
||||
pa_core_assert_ref(c);
|
||||
|
||||
pa_assert_se(ev = dbus_watch_get_data(watch));
|
||||
|
||||
/* get_watch_flags() checks if the watch is enabled */
|
||||
c->mainloop->io_enable(ev, get_watch_flags(watch));
|
||||
}
|
||||
|
||||
/* DBusAddTimeoutFunction callback for pa mainloop */
|
||||
static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data)
|
||||
{
|
||||
struct timeval tv;
|
||||
static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) {
|
||||
pa_core *c = PA_CORE(data);
|
||||
pa_time_event *ev;
|
||||
pa_core *c = (pa_core*) data;
|
||||
struct timeval tv;
|
||||
|
||||
pa_assert(timeout);
|
||||
pa_assert(c);
|
||||
|
||||
if (!dbus_timeout_get_enabled(timeout))
|
||||
return FALSE;
|
||||
|
||||
if (!pa_gettimeofday(&tv))
|
||||
return -1;
|
||||
|
||||
pa_gettimeofday(&tv);
|
||||
pa_timeval_add(&tv, dbus_timeout_get_interval(timeout) * 1000);
|
||||
|
||||
ev = c->mainloop->time_new(c->mainloop, &tv, handle_time_event,
|
||||
(void*) timeout);
|
||||
if (NULL == ev)
|
||||
return FALSE;
|
||||
ev = c->mainloop->time_new(c->mainloop, &tv, handle_time_event, timeout);
|
||||
|
||||
/* dbus_timeout_set_data(timeout, (void*) ev, c->mainloop->time_free); */
|
||||
dbus_timeout_set_data(timeout, (void*) ev, NULL);
|
||||
dbus_timeout_set_data(timeout, ev, NULL);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* DBusRemoveTimeoutFunction callback for pa mainloop */
|
||||
static void remove_timeout(DBusTimeout *timeout, void *data)
|
||||
{
|
||||
pa_core *c = (pa_core*) data;
|
||||
pa_time_event *ev = (pa_time_event*) dbus_timeout_get_data(timeout);
|
||||
static void remove_timeout(DBusTimeout *timeout, void *data) {
|
||||
pa_core *c = PA_CORE(data);
|
||||
pa_time_event *ev;
|
||||
|
||||
/* free the event */
|
||||
if (NULL != ev)
|
||||
pa_assert(timeout);
|
||||
pa_assert(c);
|
||||
|
||||
if ((ev = dbus_timeout_get_data(timeout)))
|
||||
c->mainloop->time_free(ev);
|
||||
}
|
||||
|
||||
/* DBusTimeoutToggledFunction callback for pa mainloop */
|
||||
static void toggle_timeout(DBusTimeout *timeout, void *data)
|
||||
{
|
||||
struct timeval tv;
|
||||
pa_core *c = (pa_core*) data;
|
||||
pa_time_event *ev = (pa_time_event*) dbus_timeout_get_data(timeout);
|
||||
static void toggle_timeout(DBusTimeout *timeout, void *data) {
|
||||
pa_core *c = PA_CORE(data);
|
||||
pa_time_event *ev;
|
||||
|
||||
pa_assert(timeout);
|
||||
pa_assert(c);
|
||||
|
||||
pa_assert_se(ev = dbus_timeout_get_data(timeout));
|
||||
|
||||
if (dbus_timeout_get_enabled(timeout)) {
|
||||
struct timeval tv;
|
||||
|
||||
pa_gettimeofday(&tv);
|
||||
pa_timeval_add(&tv, dbus_timeout_get_interval(timeout) * 1000);
|
||||
|
||||
c->mainloop->time_restart(ev, &tv);
|
||||
} else {
|
||||
/* disable the timeout */
|
||||
} else
|
||||
c->mainloop->time_restart(ev, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
pa_dbus_connection_free(pa_dbus_connection *c)
|
||||
{
|
||||
assert(c);
|
||||
assert(!dbus_connection_get_is_connected(c->connection));
|
||||
static void wakeup_main(void *userdata) {
|
||||
pa_dbus_connection *c = userdata;
|
||||
|
||||
pa_assert(c);
|
||||
|
||||
/* this will wakeup the mainloop and dispatch events, although
|
||||
* it may not be the cleanest way of accomplishing it */
|
||||
c->core->mainloop->defer_enable(c->dispatch_event, 1);
|
||||
}
|
||||
|
||||
static pa_dbus_connection* pa_dbus_connection_new(pa_core* c, DBusConnection *conn, const char* name) {
|
||||
pa_dbus_connection *pconn;
|
||||
|
||||
pconn = pa_xnew(pa_dbus_connection, 1);
|
||||
PA_REFCNT_INIT(pconn);
|
||||
pconn->core = c;
|
||||
pconn->property_name = name;
|
||||
pconn->connection = conn;
|
||||
pconn->dispatch_event = c->mainloop->defer_new(c->mainloop, dispatch_cb, conn);
|
||||
|
||||
pa_property_set(c, name, pconn);
|
||||
|
||||
return pconn;
|
||||
}
|
||||
|
||||
DBusConnection* pa_dbus_connection_get(pa_dbus_connection *c){
|
||||
pa_assert(c);
|
||||
pa_assert(PA_REFCNT_VALUE(c) > 0);
|
||||
pa_assert(c->connection);
|
||||
|
||||
return c->connection;
|
||||
}
|
||||
|
||||
void pa_dbus_connection_unref(pa_dbus_connection *c) {
|
||||
pa_assert(c);
|
||||
pa_assert(PA_REFCNT_VALUE(c) > 0);
|
||||
|
||||
if (PA_REFCNT_DEC(c) > 0)
|
||||
return;
|
||||
|
||||
if (dbus_connection_get_is_connected(c->connection)) {
|
||||
dbus_connection_close(c->connection);
|
||||
/* must process remaining messages, bit of a kludge to handle
|
||||
* both unload and shutdown */
|
||||
while (dbus_connection_read_write_dispatch(c->connection, -1));
|
||||
}
|
||||
|
||||
/* already disconnected, just free */
|
||||
pa_property_remove(c->core, c->property_name);
|
||||
|
|
@ -235,113 +290,39 @@ pa_dbus_connection_free(pa_dbus_connection *c)
|
|||
pa_xfree(c);
|
||||
}
|
||||
|
||||
static void
|
||||
wakeup_main(void *userdata)
|
||||
{
|
||||
pa_dbus_connection *c = (pa_dbus_connection*) userdata;
|
||||
/* this will wakeup the mainloop and dispatch events, although
|
||||
* it may not be the cleanest way of accomplishing it */
|
||||
c->core->mainloop->defer_enable(c->dispatch_event, 1);
|
||||
}
|
||||
pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *c) {
|
||||
pa_assert(c);
|
||||
pa_assert(PA_REFCNT_VALUE(c) > 0);
|
||||
|
||||
static pa_dbus_connection* pa_dbus_connection_new(pa_core* c, DBusConnection *conn, const char* name)
|
||||
{
|
||||
pa_dbus_connection *pconn = pa_xnew(pa_dbus_connection, 1);
|
||||
|
||||
pconn->refcount = 1;
|
||||
pconn->core = c;
|
||||
pconn->property_name = name;
|
||||
pconn->connection = conn;
|
||||
pconn->dispatch_event = c->mainloop->defer_new(c->mainloop, dispatch_cb,
|
||||
(void*) conn);
|
||||
|
||||
pa_property_set(c, name, pconn);
|
||||
|
||||
return pconn;
|
||||
}
|
||||
|
||||
DBusConnection* pa_dbus_connection_get(pa_dbus_connection *c)
|
||||
{
|
||||
assert(c && c->connection);
|
||||
return c->connection;
|
||||
}
|
||||
|
||||
void pa_dbus_connection_unref(pa_dbus_connection *c)
|
||||
{
|
||||
assert(c);
|
||||
|
||||
/* non-zero refcount, still outstanding refs */
|
||||
if (--(c->refcount))
|
||||
return;
|
||||
|
||||
/* refcount is zero */
|
||||
if (dbus_connection_get_is_connected(c->connection)) {
|
||||
/* disconnect as we have no more internal references */
|
||||
dbus_connection_close(c->connection);
|
||||
/* must process remaining messages, bit of a kludge to
|
||||
* handle both unload and shutdown */
|
||||
while(dbus_connection_read_write_dispatch(c->connection, -1));
|
||||
}
|
||||
pa_dbus_connection_free(c);
|
||||
}
|
||||
|
||||
pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *c)
|
||||
{
|
||||
assert(c);
|
||||
|
||||
++(c->refcount);
|
||||
PA_REFCNT_INC(c);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type,
|
||||
DBusError *error)
|
||||
{
|
||||
const char* name;
|
||||
pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, DBusError *error) {
|
||||
|
||||
static const char *const prop_name[] = {
|
||||
[DBUS_BUS_SESSION] = "dbus-connection-session",
|
||||
[DBUS_BUS_SYSTEM] = "dbus-connection-system",
|
||||
[DBUS_BUS_STARTER] = "dbus-connection-starter"
|
||||
};
|
||||
DBusConnection *conn;
|
||||
pa_dbus_connection *pconn;
|
||||
|
||||
switch (type) {
|
||||
case DBUS_BUS_SYSTEM:
|
||||
name = "dbus-connection-system";
|
||||
break;
|
||||
case DBUS_BUS_SESSION:
|
||||
name = "dbus-connection-session";
|
||||
break;
|
||||
case DBUS_BUS_STARTER:
|
||||
name = "dbus-connection-starter";
|
||||
break;
|
||||
default:
|
||||
assert(0); /* never reached */
|
||||
break;
|
||||
}
|
||||
pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER);
|
||||
|
||||
if ((pconn = pa_property_get(c, name)))
|
||||
if ((pconn = pa_property_get(c, prop_name[type])))
|
||||
return pa_dbus_connection_ref(pconn);
|
||||
|
||||
/* else */
|
||||
conn = dbus_bus_get_private(type, error);
|
||||
if (conn == NULL || dbus_error_is_set(error)) {
|
||||
if (!(conn = dbus_bus_get_private(type, error)))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pconn = pa_dbus_connection_new(c, conn, name);
|
||||
pconn = pa_dbus_connection_new(c, conn, prop_name[type]);
|
||||
|
||||
/* don't exit on disconnect */
|
||||
dbus_connection_set_exit_on_disconnect(conn, FALSE);
|
||||
/* set up the DBUS call backs */
|
||||
dbus_connection_set_dispatch_status_function(conn, dispatch_status,
|
||||
(void*) pconn, NULL);
|
||||
dbus_connection_set_watch_functions(conn,
|
||||
add_watch,
|
||||
remove_watch,
|
||||
toggle_watch,
|
||||
(void*) c, NULL);
|
||||
dbus_connection_set_timeout_functions(conn,
|
||||
add_timeout,
|
||||
remove_timeout,
|
||||
toggle_timeout,
|
||||
(void*) c, NULL);
|
||||
dbus_connection_set_dispatch_status_function(conn, dispatch_status, pconn, NULL);
|
||||
dbus_connection_set_watch_functions(conn, add_watch, remove_watch, toggle_watch, c, NULL);
|
||||
dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, toggle_timeout, c, NULL);
|
||||
dbus_connection_set_wakeup_main_function(conn, wakeup_main, pconn, NULL);
|
||||
|
||||
return pconn;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@
|
|||
#include <gconf/gconf-client.h>
|
||||
#include <glib.h>
|
||||
|
||||
#include <pulsecore/core-util.h>
|
||||
|
||||
#define PA_GCONF_ROOT "/system/pulseaudio"
|
||||
#define PA_GCONF_PATH_MODULES PA_GCONF_ROOT"/modules"
|
||||
|
||||
|
|
@ -40,13 +42,13 @@ static void handle_module(GConfClient *client, const char *name) {
|
|||
gboolean enabled, locked;
|
||||
int i;
|
||||
|
||||
snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/locked", name);
|
||||
pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/locked", name);
|
||||
locked = gconf_client_get_bool(client, p, FALSE);
|
||||
|
||||
if (locked)
|
||||
return;
|
||||
|
||||
snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/enabled", name);
|
||||
pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/enabled", name);
|
||||
enabled = gconf_client_get_bool(client, p, FALSE);
|
||||
|
||||
printf("%c%s%c", enabled ? '+' : '-', name, 0);
|
||||
|
|
@ -56,11 +58,11 @@ static void handle_module(GConfClient *client, const char *name) {
|
|||
for (i = 0; i < 10; i++) {
|
||||
gchar *n, *a;
|
||||
|
||||
snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/name%i", name, i);
|
||||
pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/name%i", name, i);
|
||||
if (!(n = gconf_client_get_string(client, p, NULL)) || !*n)
|
||||
break;
|
||||
|
||||
snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/args%i", name, i);
|
||||
pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/args%i", name, i);
|
||||
a = gconf_client_get_string(client, p, NULL);
|
||||
|
||||
printf("%s%c%s%c", n, 0, a ? a : "", 0);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -34,6 +33,7 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#ifdef HAVE_SYS_PRCTL_H
|
||||
#include <sys/prctl.h>
|
||||
|
|
@ -95,7 +95,7 @@ struct userdata {
|
|||
|
||||
static int fill_buf(struct userdata *u) {
|
||||
ssize_t r;
|
||||
assert(u);
|
||||
pa_assert(u);
|
||||
|
||||
if (u->buf_fill >= BUF_MAX) {
|
||||
pa_log("read buffer overflow");
|
||||
|
|
@ -111,21 +111,21 @@ static int fill_buf(struct userdata *u) {
|
|||
|
||||
static int read_byte(struct userdata *u) {
|
||||
int ret;
|
||||
assert(u);
|
||||
pa_assert(u);
|
||||
|
||||
if (u->buf_fill < 1)
|
||||
if (fill_buf(u) < 0)
|
||||
return -1;
|
||||
|
||||
ret = u->buf[0];
|
||||
assert(u->buf_fill > 0);
|
||||
pa_assert(u->buf_fill > 0);
|
||||
u->buf_fill--;
|
||||
memmove(u->buf, u->buf+1, u->buf_fill);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *read_string(struct userdata *u) {
|
||||
assert(u);
|
||||
pa_assert(u);
|
||||
|
||||
for (;;) {
|
||||
char *e;
|
||||
|
|
@ -143,9 +143,9 @@ static char *read_string(struct userdata *u) {
|
|||
}
|
||||
|
||||
static void unload_one_module(struct userdata *u, struct module_info*m, unsigned i) {
|
||||
assert(u);
|
||||
assert(m);
|
||||
assert(i < m->n_items);
|
||||
pa_assert(u);
|
||||
pa_assert(m);
|
||||
pa_assert(i < m->n_items);
|
||||
|
||||
if (m->items[i].index == PA_INVALID_INDEX)
|
||||
return;
|
||||
|
|
@ -161,8 +161,8 @@ static void unload_one_module(struct userdata *u, struct module_info*m, unsigned
|
|||
static void unload_all_modules(struct userdata *u, struct module_info*m) {
|
||||
unsigned i;
|
||||
|
||||
assert(u);
|
||||
assert(m);
|
||||
pa_assert(u);
|
||||
pa_assert(m);
|
||||
|
||||
for (i = 0; i < m->n_items; i++)
|
||||
unload_one_module(u, m, i);
|
||||
|
|
@ -180,10 +180,10 @@ static void load_module(
|
|||
|
||||
pa_module *mod;
|
||||
|
||||
assert(u);
|
||||
assert(m);
|
||||
assert(name);
|
||||
assert(args);
|
||||
pa_assert(u);
|
||||
pa_assert(m);
|
||||
pa_assert(name);
|
||||
pa_assert(args);
|
||||
|
||||
if (!is_new) {
|
||||
if (m->items[i].index != PA_INVALID_INDEX &&
|
||||
|
|
@ -212,8 +212,8 @@ static void module_info_free(void *p, void *userdata) {
|
|||
struct module_info *m = p;
|
||||
struct userdata *u = userdata;
|
||||
|
||||
assert(m);
|
||||
assert(u);
|
||||
pa_assert(m);
|
||||
pa_assert(u);
|
||||
|
||||
unload_all_modules(u, m);
|
||||
pa_xfree(m->name);
|
||||
|
|
@ -356,8 +356,10 @@ static int start_client(const char *n, pid_t *pid) {
|
|||
|
||||
return pipe_fds[0];
|
||||
} else {
|
||||
#ifdef __linux__
|
||||
DIR* d;
|
||||
#endif
|
||||
int max_fd, i;
|
||||
|
||||
/* child */
|
||||
|
||||
close(pipe_fds[0]);
|
||||
|
|
@ -372,18 +374,48 @@ static int start_client(const char *n, pid_t *pid) {
|
|||
close(2);
|
||||
open("/dev/null", O_WRONLY);
|
||||
|
||||
max_fd = 1024;
|
||||
#ifdef __linux__
|
||||
|
||||
if ((d = opendir("/proc/self/fd/"))) {
|
||||
|
||||
struct dirent *de;
|
||||
|
||||
while ((de = readdir(d))) {
|
||||
char *e = NULL;
|
||||
int fd;
|
||||
|
||||
if (de->d_name[0] == '.')
|
||||
continue;
|
||||
|
||||
errno = 0;
|
||||
fd = strtol(de->d_name, &e, 10);
|
||||
pa_assert(errno == 0 && e && *e == 0);
|
||||
|
||||
if (fd >= 3 && dirfd(d) != fd)
|
||||
close(fd);
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
} else {
|
||||
|
||||
#ifdef HAVE_SYS_RESOURCE_H
|
||||
{
|
||||
struct rlimit r;
|
||||
if (getrlimit(RLIMIT_NOFILE, &r) == 0)
|
||||
max_fd = r.rlim_max;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (i = 3; i < max_fd; i++)
|
||||
close(i);
|
||||
max_fd = 1024;
|
||||
|
||||
#ifdef HAVE_SYS_RESOURCE_H
|
||||
{
|
||||
struct rlimit r;
|
||||
if (getrlimit(RLIMIT_NOFILE, &r) == 0)
|
||||
max_fd = r.rlim_max;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (i = 3; i < max_fd; i++)
|
||||
close(i);
|
||||
#
|
||||
#ifdef __linux__
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PR_SET_PDEATHSIG
|
||||
/* On Linux we can use PR_SET_PDEATHSIG to have the helper
|
||||
|
|
@ -413,12 +445,12 @@ fail:
|
|||
return -1;
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
struct userdata *u;
|
||||
int r;
|
||||
|
||||
u = pa_xnew(struct userdata, 1);
|
||||
u->core = c;
|
||||
u->core = m->core;
|
||||
u->module = m;
|
||||
m->userdata = u;
|
||||
u->module_infos = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
|
||||
|
|
@ -431,8 +463,8 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
if ((u->fd = start_client(PA_GCONF_HELPER, &u->pid)) < 0)
|
||||
goto fail;
|
||||
|
||||
u->io_event = c->mainloop->io_new(
|
||||
c->mainloop,
|
||||
u->io_event = m->core->mainloop->io_new(
|
||||
m->core->mainloop,
|
||||
u->fd,
|
||||
PA_IO_EVENT_INPUT,
|
||||
io_event_cb,
|
||||
|
|
@ -449,21 +481,20 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
return 0;
|
||||
|
||||
fail:
|
||||
pa__done(c, m);
|
||||
pa__done(m);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
||||
if (u->io_event)
|
||||
c->mainloop->io_free(u->io_event);
|
||||
m->core->mainloop->io_free(u->io_event);
|
||||
|
||||
if (u->fd >= 0)
|
||||
close(u->fd);
|
||||
|
|
|
|||
603
src/modules/ladspa.h
Normal file
603
src/modules/ladspa.h
Normal file
|
|
@ -0,0 +1,603 @@
|
|||
/* ladspa.h
|
||||
|
||||
Linux Audio Developer's Simple Plugin API Version 1.1[LGPL].
|
||||
Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis,
|
||||
Stefan Westerfeld.
|
||||
|
||||
This library 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.1 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
This library 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
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
USA. */
|
||||
|
||||
#ifndef LADSPA_INCLUDED
|
||||
#define LADSPA_INCLUDED
|
||||
|
||||
#define LADSPA_VERSION "1.1"
|
||||
#define LADSPA_VERSION_MAJOR 1
|
||||
#define LADSPA_VERSION_MINOR 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Overview:
|
||||
|
||||
There is a large number of synthesis packages in use or development
|
||||
on the Linux platform at this time. This API (`The Linux Audio
|
||||
Developer's Simple Plugin API') attempts to give programmers the
|
||||
ability to write simple `plugin' audio processors in C/C++ and link
|
||||
them dynamically (`plug') into a range of these packages (`hosts').
|
||||
It should be possible for any host and any plugin to communicate
|
||||
completely through this interface.
|
||||
|
||||
This API is deliberately short and simple. To achieve compatibility
|
||||
with a range of promising Linux sound synthesis packages it
|
||||
attempts to find the `greatest common divisor' in their logical
|
||||
behaviour. Having said this, certain limiting decisions are
|
||||
implicit, notably the use of a fixed type (LADSPA_Data) for all
|
||||
data transfer and absence of a parameterised `initialisation'
|
||||
phase. See below for the LADSPA_Data typedef.
|
||||
|
||||
Plugins are expected to distinguish between control and audio
|
||||
data. Plugins have `ports' that are inputs or outputs for audio or
|
||||
control data and each plugin is `run' for a `block' corresponding
|
||||
to a short time interval measured in samples. Audio data is
|
||||
communicated using arrays of LADSPA_Data, allowing a block of audio
|
||||
to be processed by the plugin in a single pass. Control data is
|
||||
communicated using single LADSPA_Data values. Control data has a
|
||||
single value at the start of a call to the `run()' or `run_adding()'
|
||||
function, and may be considered to remain this value for its
|
||||
duration. The plugin may assume that all its input and output ports
|
||||
have been connected to the relevant data location (see the
|
||||
`connect_port()' function below) before it is asked to run.
|
||||
|
||||
Plugins will reside in shared object files suitable for dynamic
|
||||
linking by dlopen() and family. The file will provide a number of
|
||||
`plugin types' that can be used to instantiate actual plugins
|
||||
(sometimes known as `plugin instances') that can be connected
|
||||
together to perform tasks.
|
||||
|
||||
This API contains very limited error-handling. */
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Fundamental data type passed in and out of plugin. This data type
|
||||
is used to communicate audio samples and control values. It is
|
||||
assumed that the plugin will work sensibly given any numeric input
|
||||
value although it may have a preferred range (see hints below).
|
||||
|
||||
For audio it is generally assumed that 1.0f is the `0dB' reference
|
||||
amplitude and is a `normal' signal level. */
|
||||
|
||||
typedef float LADSPA_Data;
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Special Plugin Properties:
|
||||
|
||||
Optional features of the plugin type are encapsulated in the
|
||||
LADSPA_Properties type. This is assembled by ORing individual
|
||||
properties together. */
|
||||
|
||||
typedef int LADSPA_Properties;
|
||||
|
||||
/* Property LADSPA_PROPERTY_REALTIME indicates that the plugin has a
|
||||
real-time dependency (e.g. listens to a MIDI device) and so its
|
||||
output must not be cached or subject to significant latency. */
|
||||
#define LADSPA_PROPERTY_REALTIME 0x1
|
||||
|
||||
/* Property LADSPA_PROPERTY_INPLACE_BROKEN indicates that the plugin
|
||||
may cease to work correctly if the host elects to use the same data
|
||||
location for both input and output (see connect_port()). This
|
||||
should be avoided as enabling this flag makes it impossible for
|
||||
hosts to use the plugin to process audio `in-place.' */
|
||||
#define LADSPA_PROPERTY_INPLACE_BROKEN 0x2
|
||||
|
||||
/* Property LADSPA_PROPERTY_HARD_RT_CAPABLE indicates that the plugin
|
||||
is capable of running not only in a conventional host but also in a
|
||||
`hard real-time' environment. To qualify for this the plugin must
|
||||
satisfy all of the following:
|
||||
|
||||
(1) The plugin must not use malloc(), free() or other heap memory
|
||||
management within its run() or run_adding() functions. All new
|
||||
memory used in run() must be managed via the stack. These
|
||||
restrictions only apply to the run() function.
|
||||
|
||||
(2) The plugin will not attempt to make use of any library
|
||||
functions with the exceptions of functions in the ANSI standard C
|
||||
and C maths libraries, which the host is expected to provide.
|
||||
|
||||
(3) The plugin will not access files, devices, pipes, sockets, IPC
|
||||
or any other mechanism that might result in process or thread
|
||||
blocking.
|
||||
|
||||
(4) The plugin will take an amount of time to execute a run() or
|
||||
run_adding() call approximately of form (A+B*SampleCount) where A
|
||||
and B depend on the machine and host in use. This amount of time
|
||||
may not depend on input signals or plugin state. The host is left
|
||||
the responsibility to perform timings to estimate upper bounds for
|
||||
A and B. */
|
||||
#define LADSPA_PROPERTY_HARD_RT_CAPABLE 0x4
|
||||
|
||||
#define LADSPA_IS_REALTIME(x) ((x) & LADSPA_PROPERTY_REALTIME)
|
||||
#define LADSPA_IS_INPLACE_BROKEN(x) ((x) & LADSPA_PROPERTY_INPLACE_BROKEN)
|
||||
#define LADSPA_IS_HARD_RT_CAPABLE(x) ((x) & LADSPA_PROPERTY_HARD_RT_CAPABLE)
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Plugin Ports:
|
||||
|
||||
Plugins have `ports' that are inputs or outputs for audio or
|
||||
data. Ports can communicate arrays of LADSPA_Data (for audio
|
||||
inputs/outputs) or single LADSPA_Data values (for control
|
||||
input/outputs). This information is encapsulated in the
|
||||
LADSPA_PortDescriptor type which is assembled by ORing individual
|
||||
properties together.
|
||||
|
||||
Note that a port must be an input or an output port but not both
|
||||
and that a port must be a control or audio port but not both. */
|
||||
|
||||
typedef int LADSPA_PortDescriptor;
|
||||
|
||||
/* Property LADSPA_PORT_INPUT indicates that the port is an input. */
|
||||
#define LADSPA_PORT_INPUT 0x1
|
||||
|
||||
/* Property LADSPA_PORT_OUTPUT indicates that the port is an output. */
|
||||
#define LADSPA_PORT_OUTPUT 0x2
|
||||
|
||||
/* Property LADSPA_PORT_CONTROL indicates that the port is a control
|
||||
port. */
|
||||
#define LADSPA_PORT_CONTROL 0x4
|
||||
|
||||
/* Property LADSPA_PORT_AUDIO indicates that the port is a audio
|
||||
port. */
|
||||
#define LADSPA_PORT_AUDIO 0x8
|
||||
|
||||
#define LADSPA_IS_PORT_INPUT(x) ((x) & LADSPA_PORT_INPUT)
|
||||
#define LADSPA_IS_PORT_OUTPUT(x) ((x) & LADSPA_PORT_OUTPUT)
|
||||
#define LADSPA_IS_PORT_CONTROL(x) ((x) & LADSPA_PORT_CONTROL)
|
||||
#define LADSPA_IS_PORT_AUDIO(x) ((x) & LADSPA_PORT_AUDIO)
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Plugin Port Range Hints:
|
||||
|
||||
The host may wish to provide a representation of data entering or
|
||||
leaving a plugin (e.g. to generate a GUI automatically). To make
|
||||
this more meaningful, the plugin should provide `hints' to the host
|
||||
describing the usual values taken by the data.
|
||||
|
||||
Note that these are only hints. The host may ignore them and the
|
||||
plugin must not assume that data supplied to it is meaningful. If
|
||||
the plugin receives invalid input data it is expected to continue
|
||||
to run without failure and, where possible, produce a sensible
|
||||
output (e.g. a high-pass filter given a negative cutoff frequency
|
||||
might switch to an all-pass mode).
|
||||
|
||||
Hints are meaningful for all input and output ports but hints for
|
||||
input control ports are expected to be particularly useful.
|
||||
|
||||
More hint information is encapsulated in the
|
||||
LADSPA_PortRangeHintDescriptor type which is assembled by ORing
|
||||
individual hint types together. Hints may require further
|
||||
LowerBound and UpperBound information.
|
||||
|
||||
All the hint information for a particular port is aggregated in the
|
||||
LADSPA_PortRangeHint structure. */
|
||||
|
||||
typedef int LADSPA_PortRangeHintDescriptor;
|
||||
|
||||
/* Hint LADSPA_HINT_BOUNDED_BELOW indicates that the LowerBound field
|
||||
of the LADSPA_PortRangeHint should be considered meaningful. The
|
||||
value in this field should be considered the (inclusive) lower
|
||||
bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
|
||||
specified then the value of LowerBound should be multiplied by the
|
||||
sample rate. */
|
||||
#define LADSPA_HINT_BOUNDED_BELOW 0x1
|
||||
|
||||
/* Hint LADSPA_HINT_BOUNDED_ABOVE indicates that the UpperBound field
|
||||
of the LADSPA_PortRangeHint should be considered meaningful. The
|
||||
value in this field should be considered the (inclusive) upper
|
||||
bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
|
||||
specified then the value of UpperBound should be multiplied by the
|
||||
sample rate. */
|
||||
#define LADSPA_HINT_BOUNDED_ABOVE 0x2
|
||||
|
||||
/* Hint LADSPA_HINT_TOGGLED indicates that the data item should be
|
||||
considered a Boolean toggle. Data less than or equal to zero should
|
||||
be considered `off' or `false,' and data above zero should be
|
||||
considered `on' or `true.' LADSPA_HINT_TOGGLED may not be used in
|
||||
conjunction with any other hint except LADSPA_HINT_DEFAULT_0 or
|
||||
LADSPA_HINT_DEFAULT_1. */
|
||||
#define LADSPA_HINT_TOGGLED 0x4
|
||||
|
||||
/* Hint LADSPA_HINT_SAMPLE_RATE indicates that any bounds specified
|
||||
should be interpreted as multiples of the sample rate. For
|
||||
instance, a frequency range from 0Hz to the Nyquist frequency (half
|
||||
the sample rate) could be requested by this hint in conjunction
|
||||
with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds
|
||||
at all must support this hint to retain meaning. */
|
||||
#define LADSPA_HINT_SAMPLE_RATE 0x8
|
||||
|
||||
/* Hint LADSPA_HINT_LOGARITHMIC indicates that it is likely that the
|
||||
user will find it more intuitive to view values using a logarithmic
|
||||
scale. This is particularly useful for frequencies and gains. */
|
||||
#define LADSPA_HINT_LOGARITHMIC 0x10
|
||||
|
||||
/* Hint LADSPA_HINT_INTEGER indicates that a user interface would
|
||||
probably wish to provide a stepped control taking only integer
|
||||
values. Any bounds set should be slightly wider than the actual
|
||||
integer range required to avoid floating point rounding errors. For
|
||||
instance, the integer set {0,1,2,3} might be described as [-0.1,
|
||||
3.1]. */
|
||||
#define LADSPA_HINT_INTEGER 0x20
|
||||
|
||||
/* The various LADSPA_HINT_HAS_DEFAULT_* hints indicate a `normal'
|
||||
value for the port that is sensible as a default. For instance,
|
||||
this value is suitable for use as an initial value in a user
|
||||
interface or as a value the host might assign to a control port
|
||||
when the user has not provided one. Defaults are encoded using a
|
||||
mask so only one default may be specified for a port. Some of the
|
||||
hints make use of lower and upper bounds, in which case the
|
||||
relevant bound or bounds must be available and
|
||||
LADSPA_HINT_SAMPLE_RATE must be applied as usual. The resulting
|
||||
default must be rounded if LADSPA_HINT_INTEGER is present. Default
|
||||
values were introduced in LADSPA v1.1. */
|
||||
#define LADSPA_HINT_DEFAULT_MASK 0x3C0
|
||||
|
||||
/* This default values indicates that no default is provided. */
|
||||
#define LADSPA_HINT_DEFAULT_NONE 0x0
|
||||
|
||||
/* This default hint indicates that the suggested lower bound for the
|
||||
port should be used. */
|
||||
#define LADSPA_HINT_DEFAULT_MINIMUM 0x40
|
||||
|
||||
/* This default hint indicates that a low value between the suggested
|
||||
lower and upper bounds should be chosen. For ports with
|
||||
LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.75 +
|
||||
log(upper) * 0.25). Otherwise, this should be (lower * 0.75 + upper
|
||||
* 0.25). */
|
||||
#define LADSPA_HINT_DEFAULT_LOW 0x80
|
||||
|
||||
/* This default hint indicates that a middle value between the
|
||||
suggested lower and upper bounds should be chosen. For ports with
|
||||
LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.5 +
|
||||
log(upper) * 0.5). Otherwise, this should be (lower * 0.5 + upper *
|
||||
0.5). */
|
||||
#define LADSPA_HINT_DEFAULT_MIDDLE 0xC0
|
||||
|
||||
/* This default hint indicates that a high value between the suggested
|
||||
lower and upper bounds should be chosen. For ports with
|
||||
LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.25 +
|
||||
log(upper) * 0.75). Otherwise, this should be (lower * 0.25 + upper
|
||||
* 0.75). */
|
||||
#define LADSPA_HINT_DEFAULT_HIGH 0x100
|
||||
|
||||
/* This default hint indicates that the suggested upper bound for the
|
||||
port should be used. */
|
||||
#define LADSPA_HINT_DEFAULT_MAXIMUM 0x140
|
||||
|
||||
/* This default hint indicates that the number 0 should be used. Note
|
||||
that this default may be used in conjunction with
|
||||
LADSPA_HINT_TOGGLED. */
|
||||
#define LADSPA_HINT_DEFAULT_0 0x200
|
||||
|
||||
/* This default hint indicates that the number 1 should be used. Note
|
||||
that this default may be used in conjunction with
|
||||
LADSPA_HINT_TOGGLED. */
|
||||
#define LADSPA_HINT_DEFAULT_1 0x240
|
||||
|
||||
/* This default hint indicates that the number 100 should be used. */
|
||||
#define LADSPA_HINT_DEFAULT_100 0x280
|
||||
|
||||
/* This default hint indicates that the Hz frequency of `concert A'
|
||||
should be used. This will be 440 unless the host uses an unusual
|
||||
tuning convention, in which case it may be within a few Hz. */
|
||||
#define LADSPA_HINT_DEFAULT_440 0x2C0
|
||||
|
||||
#define LADSPA_IS_HINT_BOUNDED_BELOW(x) ((x) & LADSPA_HINT_BOUNDED_BELOW)
|
||||
#define LADSPA_IS_HINT_BOUNDED_ABOVE(x) ((x) & LADSPA_HINT_BOUNDED_ABOVE)
|
||||
#define LADSPA_IS_HINT_TOGGLED(x) ((x) & LADSPA_HINT_TOGGLED)
|
||||
#define LADSPA_IS_HINT_SAMPLE_RATE(x) ((x) & LADSPA_HINT_SAMPLE_RATE)
|
||||
#define LADSPA_IS_HINT_LOGARITHMIC(x) ((x) & LADSPA_HINT_LOGARITHMIC)
|
||||
#define LADSPA_IS_HINT_INTEGER(x) ((x) & LADSPA_HINT_INTEGER)
|
||||
|
||||
#define LADSPA_IS_HINT_HAS_DEFAULT(x) ((x) & LADSPA_HINT_DEFAULT_MASK)
|
||||
#define LADSPA_IS_HINT_DEFAULT_MINIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
|
||||
== LADSPA_HINT_DEFAULT_MINIMUM)
|
||||
#define LADSPA_IS_HINT_DEFAULT_LOW(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
|
||||
== LADSPA_HINT_DEFAULT_LOW)
|
||||
#define LADSPA_IS_HINT_DEFAULT_MIDDLE(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
|
||||
== LADSPA_HINT_DEFAULT_MIDDLE)
|
||||
#define LADSPA_IS_HINT_DEFAULT_HIGH(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
|
||||
== LADSPA_HINT_DEFAULT_HIGH)
|
||||
#define LADSPA_IS_HINT_DEFAULT_MAXIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
|
||||
== LADSPA_HINT_DEFAULT_MAXIMUM)
|
||||
#define LADSPA_IS_HINT_DEFAULT_0(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
|
||||
== LADSPA_HINT_DEFAULT_0)
|
||||
#define LADSPA_IS_HINT_DEFAULT_1(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
|
||||
== LADSPA_HINT_DEFAULT_1)
|
||||
#define LADSPA_IS_HINT_DEFAULT_100(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
|
||||
== LADSPA_HINT_DEFAULT_100)
|
||||
#define LADSPA_IS_HINT_DEFAULT_440(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
|
||||
== LADSPA_HINT_DEFAULT_440)
|
||||
|
||||
typedef struct _LADSPA_PortRangeHint {
|
||||
|
||||
/* Hints about the port. */
|
||||
LADSPA_PortRangeHintDescriptor HintDescriptor;
|
||||
|
||||
/* Meaningful when hint LADSPA_HINT_BOUNDED_BELOW is active. When
|
||||
LADSPA_HINT_SAMPLE_RATE is also active then this value should be
|
||||
multiplied by the relevant sample rate. */
|
||||
LADSPA_Data LowerBound;
|
||||
|
||||
/* Meaningful when hint LADSPA_HINT_BOUNDED_ABOVE is active. When
|
||||
LADSPA_HINT_SAMPLE_RATE is also active then this value should be
|
||||
multiplied by the relevant sample rate. */
|
||||
LADSPA_Data UpperBound;
|
||||
|
||||
} LADSPA_PortRangeHint;
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Plugin Handles:
|
||||
|
||||
This plugin handle indicates a particular instance of the plugin
|
||||
concerned. It is valid to compare this to NULL (0 for C++) but
|
||||
otherwise the host should not attempt to interpret it. The plugin
|
||||
may use it to reference internal instance data. */
|
||||
|
||||
typedef void * LADSPA_Handle;
|
||||
|
||||
/*****************************************************************************/
|
||||
|
||||
/* Descriptor for a Type of Plugin:
|
||||
|
||||
This structure is used to describe a plugin type. It provides a
|
||||
number of functions to examine the type, instantiate it, link it to
|
||||
buffers and workspaces and to run it. */
|
||||
|
||||
typedef struct _LADSPA_Descriptor {
|
||||
|
||||
/* This numeric identifier indicates the plugin type
|
||||
uniquely. Plugin programmers may reserve ranges of IDs from a
|
||||
central body to avoid clashes. Hosts may assume that IDs are
|
||||
below 0x1000000. */
|
||||
unsigned long UniqueID;
|
||||
|
||||
/* This identifier can be used as a unique, case-sensitive
|
||||
identifier for the plugin type within the plugin file. Plugin
|
||||
types should be identified by file and label rather than by index
|
||||
or plugin name, which may be changed in new plugin
|
||||
versions. Labels must not contain white-space characters. */
|
||||
const char * Label;
|
||||
|
||||
/* This indicates a number of properties of the plugin. */
|
||||
LADSPA_Properties Properties;
|
||||
|
||||
/* This member points to the null-terminated name of the plugin
|
||||
(e.g. "Sine Oscillator"). */
|
||||
const char * Name;
|
||||
|
||||
/* This member points to the null-terminated string indicating the
|
||||
maker of the plugin. This can be an empty string but not NULL. */
|
||||
const char * Maker;
|
||||
|
||||
/* This member points to the null-terminated string indicating any
|
||||
copyright applying to the plugin. If no Copyright applies the
|
||||
string "None" should be used. */
|
||||
const char * Copyright;
|
||||
|
||||
/* This indicates the number of ports (input AND output) present on
|
||||
the plugin. */
|
||||
unsigned long PortCount;
|
||||
|
||||
/* This member indicates an array of port descriptors. Valid indices
|
||||
vary from 0 to PortCount-1. */
|
||||
const LADSPA_PortDescriptor * PortDescriptors;
|
||||
|
||||
/* This member indicates an array of null-terminated strings
|
||||
describing ports (e.g. "Frequency (Hz)"). Valid indices vary from
|
||||
0 to PortCount-1. */
|
||||
const char * const * PortNames;
|
||||
|
||||
/* This member indicates an array of range hints for each port (see
|
||||
above). Valid indices vary from 0 to PortCount-1. */
|
||||
const LADSPA_PortRangeHint * PortRangeHints;
|
||||
|
||||
/* This may be used by the plugin developer to pass any custom
|
||||
implementation data into an instantiate call. It must not be used
|
||||
or interpreted by the host. It is expected that most plugin
|
||||
writers will not use this facility as LADSPA_Handle should be
|
||||
used to hold instance data. */
|
||||
void * ImplementationData;
|
||||
|
||||
/* This member is a function pointer that instantiates a plugin. A
|
||||
handle is returned indicating the new plugin instance. The
|
||||
instantiation function accepts a sample rate as a parameter. The
|
||||
plugin descriptor from which this instantiate function was found
|
||||
must also be passed. This function must return NULL if
|
||||
instantiation fails.
|
||||
|
||||
Note that instance initialisation should generally occur in
|
||||
activate() rather than here. */
|
||||
LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor,
|
||||
unsigned long SampleRate);
|
||||
|
||||
/* This member is a function pointer that connects a port on an
|
||||
instantiated plugin to a memory location at which a block of data
|
||||
for the port will be read/written. The data location is expected
|
||||
to be an array of LADSPA_Data for audio ports or a single
|
||||
LADSPA_Data value for control ports. Memory issues will be
|
||||
managed by the host. The plugin must read/write the data at these
|
||||
locations every time run() or run_adding() is called and the data
|
||||
present at the time of this connection call should not be
|
||||
considered meaningful.
|
||||
|
||||
connect_port() may be called more than once for a plugin instance
|
||||
to allow the host to change the buffers that the plugin is
|
||||
reading or writing. These calls may be made before or after
|
||||
activate() or deactivate() calls.
|
||||
|
||||
connect_port() must be called at least once for each port before
|
||||
run() or run_adding() is called. When working with blocks of
|
||||
LADSPA_Data the plugin should pay careful attention to the block
|
||||
size passed to the run function as the block allocated may only
|
||||
just be large enough to contain the block of samples.
|
||||
|
||||
Plugin writers should be aware that the host may elect to use the
|
||||
same buffer for more than one port and even use the same buffer
|
||||
for both input and output (see LADSPA_PROPERTY_INPLACE_BROKEN).
|
||||
However, overlapped buffers or use of a single buffer for both
|
||||
audio and control data may result in unexpected behaviour. */
|
||||
void (*connect_port)(LADSPA_Handle Instance,
|
||||
unsigned long Port,
|
||||
LADSPA_Data * DataLocation);
|
||||
|
||||
/* This member is a function pointer that initialises a plugin
|
||||
instance and activates it for use. This is separated from
|
||||
instantiate() to aid real-time support and so that hosts can
|
||||
reinitialise a plugin instance by calling deactivate() and then
|
||||
activate(). In this case the plugin instance must reset all state
|
||||
information dependent on the history of the plugin instance
|
||||
except for any data locations provided by connect_port() and any
|
||||
gain set by set_run_adding_gain(). If there is nothing for
|
||||
activate() to do then the plugin writer may provide a NULL rather
|
||||
than an empty function.
|
||||
|
||||
When present, hosts must call this function once before run() (or
|
||||
run_adding()) is called for the first time. This call should be
|
||||
made as close to the run() call as possible and indicates to
|
||||
real-time plugins that they are now live. Plugins should not rely
|
||||
on a prompt call to run() after activate(). activate() may not be
|
||||
called again unless deactivate() is called first. Note that
|
||||
connect_port() may be called before or after a call to
|
||||
activate(). */
|
||||
void (*activate)(LADSPA_Handle Instance);
|
||||
|
||||
/* This method is a function pointer that runs an instance of a
|
||||
plugin for a block. Two parameters are required: the first is a
|
||||
handle to the particular instance to be run and the second
|
||||
indicates the block size (in samples) for which the plugin
|
||||
instance may run.
|
||||
|
||||
Note that if an activate() function exists then it must be called
|
||||
before run() or run_adding(). If deactivate() is called for a
|
||||
plugin instance then the plugin instance may not be reused until
|
||||
activate() has been called again.
|
||||
|
||||
If the plugin has the property LADSPA_PROPERTY_HARD_RT_CAPABLE
|
||||
then there are various things that the plugin should not do
|
||||
within the run() or run_adding() functions (see above). */
|
||||
void (*run)(LADSPA_Handle Instance,
|
||||
unsigned long SampleCount);
|
||||
|
||||
/* This method is a function pointer that runs an instance of a
|
||||
plugin for a block. This has identical behaviour to run() except
|
||||
in the way data is output from the plugin. When run() is used,
|
||||
values are written directly to the memory areas associated with
|
||||
the output ports. However when run_adding() is called, values
|
||||
must be added to the values already present in the memory
|
||||
areas. Furthermore, output values written must be scaled by the
|
||||
current gain set by set_run_adding_gain() (see below) before
|
||||
addition.
|
||||
|
||||
run_adding() is optional. When it is not provided by a plugin,
|
||||
this function pointer must be set to NULL. When it is provided,
|
||||
the function set_run_adding_gain() must be provided also. */
|
||||
void (*run_adding)(LADSPA_Handle Instance,
|
||||
unsigned long SampleCount);
|
||||
|
||||
/* This method is a function pointer that sets the output gain for
|
||||
use when run_adding() is called (see above). If this function is
|
||||
never called the gain is assumed to default to 1. Gain
|
||||
information should be retained when activate() or deactivate()
|
||||
are called.
|
||||
|
||||
This function should be provided by the plugin if and only if the
|
||||
run_adding() function is provided. When it is absent this
|
||||
function pointer must be set to NULL. */
|
||||
void (*set_run_adding_gain)(LADSPA_Handle Instance,
|
||||
LADSPA_Data Gain);
|
||||
|
||||
/* This is the counterpart to activate() (see above). If there is
|
||||
nothing for deactivate() to do then the plugin writer may provide
|
||||
a NULL rather than an empty function.
|
||||
|
||||
Hosts must deactivate all activated units after they have been
|
||||
run() (or run_adding()) for the last time. This call should be
|
||||
made as close to the last run() call as possible and indicates to
|
||||
real-time plugins that they are no longer live. Plugins should
|
||||
not rely on prompt deactivation. Note that connect_port() may be
|
||||
called before or after a call to deactivate().
|
||||
|
||||
Deactivation is not similar to pausing as the plugin instance
|
||||
will be reinitialised when activate() is called to reuse it. */
|
||||
void (*deactivate)(LADSPA_Handle Instance);
|
||||
|
||||
/* Once an instance of a plugin has been finished with it can be
|
||||
deleted using the following function. The instance handle passed
|
||||
ceases to be valid after this call.
|
||||
|
||||
If activate() was called for a plugin instance then a
|
||||
corresponding call to deactivate() must be made before cleanup()
|
||||
is called. */
|
||||
void (*cleanup)(LADSPA_Handle Instance);
|
||||
|
||||
} LADSPA_Descriptor;
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
/* Accessing a Plugin: */
|
||||
|
||||
/* The exact mechanism by which plugins are loaded is host-dependent,
|
||||
however all most hosts will need to know is the name of shared
|
||||
object file containing the plugin types. To allow multiple hosts to
|
||||
share plugin types, hosts may wish to check for environment
|
||||
variable LADSPA_PATH. If present, this should contain a
|
||||
colon-separated path indicating directories that should be searched
|
||||
(in order) when loading plugin types.
|
||||
|
||||
A plugin programmer must include a function called
|
||||
"ladspa_descriptor" with the following function prototype within
|
||||
the shared object file. This function will have C-style linkage (if
|
||||
you are using C++ this is taken care of by the `extern "C"' clause
|
||||
at the top of the file).
|
||||
|
||||
A host will find the plugin shared object file by one means or
|
||||
another, find the ladspa_descriptor() function, call it, and
|
||||
proceed from there.
|
||||
|
||||
Plugin types are accessed by index (not ID) using values from 0
|
||||
upwards. Out of range indexes must result in this function
|
||||
returning NULL, so the plugin count can be determined by checking
|
||||
for the least index that results in NULL being returned. */
|
||||
|
||||
const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index);
|
||||
|
||||
/* Datatype corresponding to the ladspa_descriptor() function. */
|
||||
typedef const LADSPA_Descriptor *
|
||||
(*LADSPA_Descriptor_Function)(unsigned long Index);
|
||||
|
||||
/**********************************************************************/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* LADSPA_INCLUDED */
|
||||
|
||||
/* EOF */
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -26,7 +26,6 @@
|
|||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <pulsecore/module.h>
|
||||
|
|
@ -35,6 +34,7 @@
|
|||
#include <pulsecore/sioman.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/macro.h>
|
||||
|
||||
#include "module-cli-symdef.h"
|
||||
|
||||
|
|
@ -51,8 +51,8 @@ static const char* const valid_modargs[] = {
|
|||
static void eof_and_unload_cb(pa_cli*c, void *userdata) {
|
||||
pa_module *m = userdata;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(c);
|
||||
pa_assert(m);
|
||||
|
||||
pa_module_unload_request(m);
|
||||
}
|
||||
|
|
@ -60,21 +60,20 @@ static void eof_and_unload_cb(pa_cli*c, void *userdata) {
|
|||
static void eof_and_exit_cb(pa_cli*c, void *userdata) {
|
||||
pa_module *m = userdata;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(c);
|
||||
pa_assert(m);
|
||||
|
||||
m->core->mainloop->quit(m->core->mainloop, 0);
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
pa_iochannel *io;
|
||||
pa_modargs *ma;
|
||||
int exit_on_eof = 0;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(m);
|
||||
|
||||
if (c->running_as_daemon) {
|
||||
if (m->core->running_as_daemon) {
|
||||
pa_log_info("Running as daemon, refusing to load this module.");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -94,12 +93,10 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
io = pa_iochannel_new(c->mainloop, STDIN_FILENO, STDOUT_FILENO);
|
||||
assert(io);
|
||||
io = pa_iochannel_new(m->core->mainloop, STDIN_FILENO, STDOUT_FILENO);
|
||||
pa_iochannel_set_noclose(io, 1);
|
||||
|
||||
m->userdata = pa_cli_new(c, io, m);
|
||||
assert(m->userdata);
|
||||
m->userdata = pa_cli_new(m->core, io, m);
|
||||
|
||||
pa_cli_set_eof_callback(m->userdata, exit_on_eof ? eof_and_exit_cb : eof_and_unload_cb, m);
|
||||
|
||||
|
|
@ -115,11 +112,10 @@ fail:
|
|||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
assert(c);
|
||||
assert(m);
|
||||
void pa__done(pa_module*m) {
|
||||
pa_assert(m);
|
||||
|
||||
if (c->running_as_daemon == 0) {
|
||||
if (m->core->running_as_daemon == 0) {
|
||||
pa_cli_free(m->userdata);
|
||||
pa_stdio_release();
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
103
src/modules/module-default-device-restore.c
Normal file
103
src/modules/module-default-device-restore.c
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/* $Id$ */
|
||||
|
||||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2006 Lennart Poettering
|
||||
|
||||
PulseAudio 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.
|
||||
|
||||
PulseAudio 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 PulseAudio; 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 <pulsecore/core-util.h>
|
||||
#include <pulsecore/module.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/namereg.h>
|
||||
|
||||
#include "module-default-device-restore-symdef.h"
|
||||
|
||||
PA_MODULE_AUTHOR("Lennart Poettering")
|
||||
PA_MODULE_DESCRIPTION("Automatically restore the default sink and source")
|
||||
PA_MODULE_VERSION(PACKAGE_VERSION)
|
||||
|
||||
#define DEFAULT_SINK_FILE "default-sink"
|
||||
#define DEFAULT_SOURCE_FILE "default-source"
|
||||
|
||||
int pa__init(pa_module *m) {
|
||||
FILE *f;
|
||||
|
||||
/* We never overwrite manually configured settings */
|
||||
|
||||
if (m->core->default_sink_name)
|
||||
pa_log_info("Manually configured default sink, not overwriting.");
|
||||
else if ((f = pa_open_config_file(NULL, DEFAULT_SINK_FILE, NULL, NULL, "r"))) {
|
||||
char ln[256] = "";
|
||||
|
||||
fgets(ln, sizeof(ln)-1, f);
|
||||
pa_strip_nl(ln);
|
||||
fclose(f);
|
||||
|
||||
if (!ln[0])
|
||||
pa_log_debug("No previous default sink setting, ignoring.");
|
||||
else if (pa_namereg_get(m->core, ln, PA_NAMEREG_SINK, 1)) {
|
||||
pa_namereg_set_default(m->core, ln, PA_NAMEREG_SINK);
|
||||
pa_log_debug("Restored default sink '%s'.", ln);
|
||||
} else
|
||||
pa_log_info("Saved default sink '%s' not existant, not restoring default sink setting.", ln);
|
||||
}
|
||||
|
||||
if (m->core->default_source_name)
|
||||
pa_log_info("Manually configured default source, not overwriting.");
|
||||
else if ((f = pa_open_config_file(NULL, DEFAULT_SOURCE_FILE, NULL, NULL, "r"))) {
|
||||
char ln[256] = "";
|
||||
|
||||
fgets(ln, sizeof(ln)-1, f);
|
||||
pa_strip_nl(ln);
|
||||
fclose(f);
|
||||
|
||||
if (!ln[0])
|
||||
pa_log_debug("No previous default source setting, ignoring.");
|
||||
else if (pa_namereg_get(m->core, ln, PA_NAMEREG_SOURCE, 1)) {
|
||||
pa_namereg_set_default(m->core, ln, PA_NAMEREG_SOURCE);
|
||||
pa_log_debug("Restored default source '%s'.", ln);
|
||||
} else
|
||||
pa_log_info("Saved default source '%s' not existant, not restoring default source setting.", ln);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pa__done(pa_module*m) {
|
||||
FILE *f;
|
||||
|
||||
if ((f = pa_open_config_file(NULL, DEFAULT_SINK_FILE, NULL, NULL, "w"))) {
|
||||
const char *n = pa_namereg_get_default_sink_name(m->core);
|
||||
fprintf(f, "%s\n", n ? n : "");
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
if ((f = pa_open_config_file(NULL, DEFAULT_SOURCE_FILE, NULL, NULL, "w"))) {
|
||||
const char *n = pa_namereg_get_default_source_name(m->core);
|
||||
fprintf(f, "%s\n", n ? n : "");
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -18,8 +18,8 @@ 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);
|
||||
int pa__init(pa_module*m);
|
||||
void pa__done(pa_module*m);
|
||||
|
||||
const char* pa__get_author(void);
|
||||
const char* pa__get_description(void);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -44,6 +43,7 @@
|
|||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/macro.h>
|
||||
|
||||
#include "module-detect-symdef.h"
|
||||
|
||||
|
|
@ -52,6 +52,11 @@ 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* const valid_modargs[] = {
|
||||
"just-one",
|
||||
NULL
|
||||
};
|
||||
|
||||
#ifdef HAVE_ALSA
|
||||
|
||||
static int detect_alsa(pa_core *c, int just_one) {
|
||||
|
|
@ -96,7 +101,7 @@ static int detect_alsa(pa_core *c, int just_one) {
|
|||
if (subdevice != 0)
|
||||
continue;
|
||||
|
||||
snprintf(args, sizeof(args), "device=hw:%u", device);
|
||||
pa_snprintf(args, sizeof(args), "device=hw:%u", device);
|
||||
if (!pa_module_load(c, is_sink ? "module-alsa-sink" : "module-alsa-source", args))
|
||||
continue;
|
||||
|
||||
|
|
@ -139,7 +144,7 @@ static int detect_oss(pa_core *c, int just_one) {
|
|||
line[strcspn(line, "\r\n")] = 0;
|
||||
|
||||
if (!b) {
|
||||
b = strcmp(line, "Audio devices:") == 0 || strcmp(line, "Installed devices:") == 0;
|
||||
b = strcmp(line, "Audio devices:") == 0 || strcmp(line, "Installed devices:") == 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -148,20 +153,20 @@ static int detect_oss(pa_core *c, int just_one) {
|
|||
|
||||
if (sscanf(line, "%u: ", &device) == 1) {
|
||||
if (device == 0)
|
||||
snprintf(args, sizeof(args), "device=/dev/dsp");
|
||||
pa_snprintf(args, sizeof(args), "device=/dev/dsp");
|
||||
else
|
||||
snprintf(args, sizeof(args), "device=/dev/dsp%u", device);
|
||||
pa_snprintf(args, sizeof(args), "device=/dev/dsp%u", device);
|
||||
|
||||
if (!pa_module_load(c, "module-oss", args))
|
||||
continue;
|
||||
|
||||
} else if (sscanf(line, "pcm%u: ", &device) == 1) {
|
||||
} else if (sscanf(line, "pcm%u: ", &device) == 1) {
|
||||
/* FreeBSD support, the devices are named /dev/dsp0.0, dsp0.1 and so on */
|
||||
snprintf(args, sizeof(args), "device=/dev/dsp%u.0", device);
|
||||
pa_snprintf(args, sizeof(args), "device=/dev/dsp%u.0", device);
|
||||
|
||||
if (!pa_module_load(c, "module-oss", args))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
n++;
|
||||
|
||||
|
|
@ -193,7 +198,7 @@ static int detect_solaris(pa_core *c, int just_one) {
|
|||
if (!S_ISCHR(s.st_mode))
|
||||
return 0;
|
||||
|
||||
snprintf(args, sizeof(args), "device=%s", dev);
|
||||
pa_snprintf(args, sizeof(args), "device=%s", dev);
|
||||
|
||||
if (!pa_module_load(c, "module-solaris", args))
|
||||
return 0;
|
||||
|
|
@ -215,17 +220,11 @@ static int detect_waveout(pa_core *c, int just_one) {
|
|||
}
|
||||
#endif
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(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);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("Failed to parse module arguments");
|
||||
|
|
@ -238,16 +237,16 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
}
|
||||
|
||||
#if HAVE_ALSA
|
||||
if ((n = detect_alsa(c, just_one)) <= 0)
|
||||
if ((n = detect_alsa(m->core, just_one)) <= 0)
|
||||
#endif
|
||||
#if HAVE_OSS
|
||||
if ((n = detect_oss(c, just_one)) <= 0)
|
||||
if ((n = detect_oss(m->core, just_one)) <= 0)
|
||||
#endif
|
||||
#if HAVE_SOLARIS
|
||||
if ((n = detect_solaris(c, just_one)) <= 0)
|
||||
if ((n = detect_solaris(m->core, just_one)) <= 0)
|
||||
#endif
|
||||
#if OS_IS_WIN32
|
||||
if ((n = detect_waveout(c, just_one)) <= 0)
|
||||
if ((n = detect_waveout(m->core, just_one)) <= 0)
|
||||
#endif
|
||||
{
|
||||
pa_log_warn("failed to detect any sound hardware.");
|
||||
|
|
@ -269,9 +268,3 @@ fail:
|
|||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void pa__done(PA_GCC_UNUSED pa_core *c, PA_GCC_UNUSED pa_module*m) {
|
||||
/* NOP */
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@
|
|||
#endif
|
||||
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
|
|
@ -48,23 +47,25 @@ static const char* const valid_modargs[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
pa_modargs *ma = NULL;
|
||||
int ret = -1, fd = -1;
|
||||
char x = 1;
|
||||
assert(c && m);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs)) ||
|
||||
pa_modargs_get_value_s32(ma, "fd", &fd) < 0 ||
|
||||
fd < 0) {
|
||||
|
||||
pa_log("Failed to parse module arguments");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (pa_loop_write(fd, &x, sizeof(x), NULL) != sizeof(x))
|
||||
pa_log("WARNING: write(%u, 1, 1) failed: %s", fd, pa_cstrerror(errno));
|
||||
pa_log_warn("write(%u, 1, 1) failed: %s", fd, pa_cstrerror(errno));
|
||||
|
||||
close(fd);
|
||||
pa_assert_se(pa_close(fd) == 0);
|
||||
|
||||
pa_module_unload_request(m);
|
||||
|
||||
|
|
@ -76,9 +77,3 @@ finish:
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
assert(c && m);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
#endif
|
||||
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
|
@ -48,11 +47,12 @@ static const char* const valid_modargs[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
pa_modargs *ma = NULL;
|
||||
int ret = -1;
|
||||
uint32_t pid = 0;
|
||||
assert(c && m);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs)) ||
|
||||
pa_modargs_get_value_u32(ma, "pid", &pid) < 0 ||
|
||||
|
|
@ -62,7 +62,7 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
}
|
||||
|
||||
if (kill(pid, SIGUSR1) < 0)
|
||||
pa_log("WARNING: kill(%u) failed: %s", pid, pa_cstrerror(errno));
|
||||
pa_log_warn("kill(%u) failed: %s", pid, pa_cstrerror(errno));
|
||||
|
||||
pa_module_unload_request(m);
|
||||
|
||||
|
|
@ -74,9 +74,3 @@ finish:
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
assert(c && m);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -28,14 +28,23 @@
|
|||
#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 <poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#ifdef HAVE_LINUX_SOCKIOS_H
|
||||
#include <linux/sockios.h>
|
||||
#endif
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
#include <pulse/timeval.h>
|
||||
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/iochannel.h>
|
||||
|
|
@ -47,27 +56,37 @@
|
|||
#include <pulsecore/socket-client.h>
|
||||
#include <pulsecore/esound.h>
|
||||
#include <pulsecore/authkey.h>
|
||||
#include <pulsecore/thread-mq.h>
|
||||
#include <pulsecore/thread.h>
|
||||
#include <pulsecore/time-smoother.h>
|
||||
#include <pulsecore/rtclock.h>
|
||||
#include <pulsecore/socket-util.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>")
|
||||
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"
|
||||
#define DEFAULT_SINK_NAME "esound_out"
|
||||
|
||||
struct userdata {
|
||||
pa_core *core;
|
||||
|
||||
pa_module *module;
|
||||
pa_sink *sink;
|
||||
pa_iochannel *io;
|
||||
pa_socket_client *client;
|
||||
|
||||
pa_defer_event *defer_event;
|
||||
pa_thread_mq thread_mq;
|
||||
pa_rtpoll *rtpoll;
|
||||
pa_rtpoll_item *rtpoll_item;
|
||||
pa_thread *thread;
|
||||
|
||||
pa_memchunk memchunk;
|
||||
pa_module *module;
|
||||
|
||||
void *write_data;
|
||||
size_t write_length, write_index;
|
||||
|
|
@ -75,12 +94,28 @@ struct userdata {
|
|||
void *read_data;
|
||||
size_t read_length, read_index;
|
||||
|
||||
enum { STATE_AUTH, STATE_LATENCY, STATE_RUNNING, STATE_DEAD } state;
|
||||
enum {
|
||||
STATE_AUTH,
|
||||
STATE_LATENCY,
|
||||
STATE_PREPARE,
|
||||
STATE_RUNNING,
|
||||
STATE_DEAD
|
||||
} state;
|
||||
|
||||
pa_usec_t latency;
|
||||
|
||||
esd_format_t format;
|
||||
int32_t rate;
|
||||
|
||||
pa_smoother *smoother;
|
||||
int fd;
|
||||
|
||||
int64_t offset;
|
||||
|
||||
pa_iochannel *io;
|
||||
pa_socket_client *client;
|
||||
|
||||
size_t block_size;
|
||||
};
|
||||
|
||||
static const char* const valid_modargs[] = {
|
||||
|
|
@ -93,42 +128,211 @@ static const char* const valid_modargs[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
static void cancel(struct userdata *u) {
|
||||
assert(u);
|
||||
enum {
|
||||
SINK_MESSAGE_PASS_SOCKET = PA_SINK_MESSAGE_MAX
|
||||
};
|
||||
|
||||
u->state = STATE_DEAD;
|
||||
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||
struct userdata *u = PA_SINK(o)->userdata;
|
||||
|
||||
if (u->io) {
|
||||
pa_iochannel_free(u->io);
|
||||
u->io = NULL;
|
||||
switch (code) {
|
||||
|
||||
case PA_SINK_MESSAGE_SET_STATE:
|
||||
|
||||
switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
|
||||
|
||||
case PA_SINK_SUSPENDED:
|
||||
pa_assert(PA_SINK_OPENED(u->sink->thread_info.state));
|
||||
|
||||
pa_smoother_pause(u->smoother, pa_rtclock_usec());
|
||||
break;
|
||||
|
||||
case PA_SINK_IDLE:
|
||||
case PA_SINK_RUNNING:
|
||||
|
||||
if (u->sink->thread_info.state == PA_SINK_SUSPENDED)
|
||||
pa_smoother_resume(u->smoother, pa_rtclock_usec());
|
||||
|
||||
break;
|
||||
|
||||
case PA_SINK_UNLINKED:
|
||||
case PA_SINK_INIT:
|
||||
;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case PA_SINK_MESSAGE_GET_LATENCY: {
|
||||
pa_usec_t w, r;
|
||||
|
||||
r = pa_smoother_get(u->smoother, pa_rtclock_usec());
|
||||
w = pa_bytes_to_usec(u->offset + u->memchunk.length, &u->sink->sample_spec);
|
||||
|
||||
*((pa_usec_t*) data) = w > r ? w - r : 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case SINK_MESSAGE_PASS_SOCKET: {
|
||||
struct pollfd *pollfd;
|
||||
|
||||
pa_assert(!u->rtpoll_item);
|
||||
|
||||
u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
|
||||
pollfd->fd = u->fd;
|
||||
pollfd->events = pollfd->revents = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (u->defer_event) {
|
||||
u->core->mainloop->defer_free(u->defer_event);
|
||||
u->defer_event = NULL;
|
||||
return pa_sink_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
static void thread_func(void *userdata) {
|
||||
struct userdata *u = userdata;
|
||||
int write_type = 0;
|
||||
|
||||
pa_assert(u);
|
||||
|
||||
pa_log_debug("Thread starting up");
|
||||
|
||||
pa_thread_mq_install(&u->thread_mq);
|
||||
pa_rtpoll_install(u->rtpoll);
|
||||
|
||||
pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec());
|
||||
|
||||
for (;;) {
|
||||
int ret;
|
||||
|
||||
if (u->rtpoll_item) {
|
||||
struct pollfd *pollfd;
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
|
||||
|
||||
/* Render some data and write it to the fifo */
|
||||
if (PA_SINK_OPENED(u->sink->thread_info.state) && pollfd->revents) {
|
||||
pa_usec_t usec;
|
||||
int64_t n;
|
||||
|
||||
for (;;) {
|
||||
ssize_t l;
|
||||
void *p;
|
||||
|
||||
if (u->memchunk.length <= 0)
|
||||
pa_sink_render(u->sink, u->block_size, &u->memchunk);
|
||||
|
||||
pa_assert(u->memchunk.length > 0);
|
||||
|
||||
p = pa_memblock_acquire(u->memchunk.memblock);
|
||||
l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type);
|
||||
pa_memblock_release(u->memchunk.memblock);
|
||||
|
||||
pa_assert(l != 0);
|
||||
|
||||
if (l < 0) {
|
||||
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
else if (errno == EAGAIN) {
|
||||
|
||||
/* OK, we filled all socket buffers up
|
||||
* now. */
|
||||
goto filled_up;
|
||||
|
||||
} else {
|
||||
pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
} else {
|
||||
u->offset += l;
|
||||
|
||||
u->memchunk.index += l;
|
||||
u->memchunk.length -= l;
|
||||
|
||||
if (u->memchunk.length <= 0) {
|
||||
pa_memblock_unref(u->memchunk.memblock);
|
||||
pa_memchunk_reset(&u->memchunk);
|
||||
}
|
||||
|
||||
pollfd->revents = 0;
|
||||
|
||||
if (u->memchunk.length > 0)
|
||||
|
||||
/* OK, we wrote less that we asked for,
|
||||
* hence we can assume that the socket
|
||||
* buffers are full now */
|
||||
goto filled_up;
|
||||
}
|
||||
}
|
||||
|
||||
filled_up:
|
||||
|
||||
/* At this spot we know that the socket buffers are
|
||||
* fully filled up. This is the best time to estimate
|
||||
* the playback position of the server */
|
||||
|
||||
n = u->offset;
|
||||
|
||||
#ifdef SIOCOUTQ
|
||||
{
|
||||
int l;
|
||||
if (ioctl(u->fd, SIOCOUTQ, &l) >= 0 && l > 0)
|
||||
n -= l;
|
||||
}
|
||||
#endif
|
||||
|
||||
usec = pa_bytes_to_usec(n, &u->sink->sample_spec);
|
||||
|
||||
if (usec > u->latency)
|
||||
usec -= u->latency;
|
||||
else
|
||||
usec = 0;
|
||||
|
||||
pa_smoother_put(u->smoother, pa_rtclock_usec(), usec);
|
||||
}
|
||||
|
||||
/* Hmm, nothing to do. Let's sleep */
|
||||
pollfd->events = PA_SINK_OPENED(u->sink->thread_info.state) ? POLLOUT : 0;
|
||||
}
|
||||
|
||||
if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
|
||||
goto fail;
|
||||
|
||||
if (ret == 0)
|
||||
goto finish;
|
||||
|
||||
if (u->rtpoll_item) {
|
||||
struct pollfd* pollfd;
|
||||
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
|
||||
|
||||
if (pollfd->revents & ~POLLOUT) {
|
||||
pa_log("FIFO shutdown.");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (u->sink) {
|
||||
pa_sink_disconnect(u->sink);
|
||||
pa_sink_unref(u->sink);
|
||||
u->sink = NULL;
|
||||
}
|
||||
fail:
|
||||
/* If this was no regular exit from the loop we have to continue
|
||||
* processing messages until we received PA_MESSAGE_SHUTDOWN */
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
|
||||
pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
|
||||
|
||||
if (u->module) {
|
||||
pa_module_unload_request(u->module);
|
||||
u->module = NULL;
|
||||
}
|
||||
finish:
|
||||
pa_log_debug("Thread shutting down");
|
||||
}
|
||||
|
||||
static int do_write(struct userdata *u) {
|
||||
ssize_t r;
|
||||
assert(u);
|
||||
pa_assert(u);
|
||||
|
||||
if (!pa_iochannel_is_writable(u->io))
|
||||
return 0;
|
||||
|
||||
if (u->write_data) {
|
||||
assert(u->write_index < u->write_length);
|
||||
pa_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("write() failed: %s", pa_cstrerror(errno));
|
||||
|
|
@ -136,45 +340,44 @@ static int do_write(struct userdata *u) {
|
|||
}
|
||||
|
||||
u->write_index += r;
|
||||
assert(u->write_index <= u->write_length);
|
||||
pa_assert(u->write_index <= u->write_length);
|
||||
|
||||
if (u->write_index == u->write_length) {
|
||||
free(u->write_data);
|
||||
pa_xfree(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_sink_used_by(u->sink));
|
||||
}
|
||||
|
||||
if (!u->memchunk.length)
|
||||
if (pa_sink_render(u->sink, 8192, &u->memchunk) < 0)
|
||||
return 0;
|
||||
if (!u->write_data && u->state == STATE_PREPARE) {
|
||||
/* OK, we're done with sending all control data we need to, so
|
||||
* let's hand the socket over to the IO thread now */
|
||||
|
||||
assert(u->memchunk.memblock && u->memchunk.length);
|
||||
pa_assert(u->fd < 0);
|
||||
u->fd = pa_iochannel_get_send_fd(u->io);
|
||||
|
||||
if ((r = pa_iochannel_write(u->io, (uint8_t*) u->memchunk.memblock->data + u->memchunk.index, u->memchunk.length)) < 0) {
|
||||
pa_log("write() failed: %s", pa_cstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
pa_iochannel_set_noclose(u->io, TRUE);
|
||||
pa_iochannel_free(u->io);
|
||||
u->io = NULL;
|
||||
|
||||
u->memchunk.index += r;
|
||||
u->memchunk.length -= r;
|
||||
pa_make_tcp_socket_low_delay(u->fd);
|
||||
|
||||
if (u->memchunk.length <= 0) {
|
||||
pa_memblock_unref(u->memchunk.memblock);
|
||||
u->memchunk.memblock = NULL;
|
||||
}
|
||||
pa_log_info("Connection authenticated, handing fd to IO thread...");
|
||||
|
||||
pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_PASS_SOCKET, NULL, 0, NULL, NULL);
|
||||
u->state = STATE_RUNNING;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int handle_response(struct userdata *u) {
|
||||
assert(u);
|
||||
pa_assert(u);
|
||||
|
||||
switch (u->state) {
|
||||
|
||||
case STATE_AUTH:
|
||||
assert(u->read_length == sizeof(int32_t));
|
||||
pa_assert(u->read_length == sizeof(int32_t));
|
||||
|
||||
/* Process auth data */
|
||||
if (!*(int32_t*) u->read_data) {
|
||||
|
|
@ -183,14 +386,14 @@ static int handle_response(struct userdata *u) {
|
|||
}
|
||||
|
||||
/* Request latency data */
|
||||
assert(!u->write_data);
|
||||
pa_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));
|
||||
pa_assert(u->read_length >= sizeof(int32_t));
|
||||
u->read_index = 0;
|
||||
u->read_length = sizeof(int32_t);
|
||||
|
||||
|
|
@ -198,17 +401,17 @@ static int handle_response(struct userdata *u) {
|
|||
|
||||
case STATE_LATENCY: {
|
||||
int32_t *p;
|
||||
assert(u->read_length == sizeof(int32_t));
|
||||
pa_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("WARNING! Invalid latency information received from server");
|
||||
pa_log_warn("Invalid latency information received from server");
|
||||
u->latency = 0;
|
||||
}
|
||||
|
||||
/* Create stream */
|
||||
assert(!u->write_data);
|
||||
pa_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;
|
||||
|
|
@ -216,7 +419,7 @@ static int handle_response(struct userdata *u) {
|
|||
pa_strlcpy((char*) p, "PulseAudio Tunnel", ESD_NAME_MAX);
|
||||
|
||||
u->write_index = 0;
|
||||
u->state = STATE_RUNNING;
|
||||
u->state = STATE_PREPARE;
|
||||
|
||||
/* Don't read any further */
|
||||
pa_xfree(u->read_data);
|
||||
|
|
@ -227,14 +430,14 @@ static int handle_response(struct userdata *u) {
|
|||
}
|
||||
|
||||
default:
|
||||
abort();
|
||||
pa_assert_not_reached();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_read(struct userdata *u) {
|
||||
assert(u);
|
||||
pa_assert(u);
|
||||
|
||||
if (!pa_iochannel_is_readable(u->io))
|
||||
return 0;
|
||||
|
|
@ -245,16 +448,15 @@ static int do_read(struct userdata *u) {
|
|||
if (!u->read_data)
|
||||
return 0;
|
||||
|
||||
assert(u->read_index < u->read_length);
|
||||
pa_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("read() failed: %s", r < 0 ? pa_cstrerror(errno) : "EOF");
|
||||
cancel(u);
|
||||
return -1;
|
||||
}
|
||||
|
||||
u->read_index += r;
|
||||
assert(u->read_index <= u->read_length);
|
||||
pa_assert(u->read_index <= u->read_length);
|
||||
|
||||
if (u->read_index == u->read_length)
|
||||
return handle_response(u);
|
||||
|
|
@ -263,42 +465,19 @@ static int do_read(struct userdata *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);
|
||||
pa_assert(u);
|
||||
|
||||
if (do_read(u) < 0 || do_write(u) < 0) {
|
||||
|
||||
if (u->io) {
|
||||
pa_iochannel_free(u->io);
|
||||
u->io = NULL;
|
||||
}
|
||||
|
||||
pa_module_unload_request(u->module);
|
||||
}
|
||||
}
|
||||
|
||||
static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, void *userdata) {
|
||||
|
|
@ -308,30 +487,34 @@ static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, vo
|
|||
u->client = NULL;
|
||||
|
||||
if (!io) {
|
||||
pa_log("connection failed: %s", pa_cstrerror(errno));
|
||||
cancel(u);
|
||||
pa_log("Connection failed: %s", pa_cstrerror(errno));
|
||||
pa_module_unload_request(u->module);
|
||||
return;
|
||||
}
|
||||
|
||||
pa_assert(!u->io);
|
||||
u->io = io;
|
||||
pa_iochannel_set_callback(u->io, io_callback, u);
|
||||
|
||||
pa_log_info("Connection established, authenticating ...");
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
struct userdata *u = NULL;
|
||||
const char *p;
|
||||
pa_sample_spec ss;
|
||||
pa_modargs *ma = NULL;
|
||||
char *t;
|
||||
const char *espeaker;
|
||||
|
||||
assert(c && m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("failed to parse module arguments");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ss = c->default_sample_spec;
|
||||
ss = m->core->default_sample_spec;
|
||||
if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
|
||||
pa_log("invalid sample format specification");
|
||||
goto fail;
|
||||
|
|
@ -343,37 +526,62 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
u = pa_xmalloc0(sizeof(struct userdata));
|
||||
u->core = c;
|
||||
u = pa_xnew0(struct userdata, 1);
|
||||
u->core = m->core;
|
||||
u->module = m;
|
||||
m->userdata = u;
|
||||
u->fd = -1;
|
||||
u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE);
|
||||
pa_memchunk_reset(&u->memchunk);
|
||||
u->offset = 0;
|
||||
|
||||
pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
|
||||
u->rtpoll = pa_rtpoll_new();
|
||||
pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
|
||||
u->rtpoll_item = NULL;
|
||||
|
||||
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->block_size = pa_usec_to_bytes(PA_USEC_PER_SEC/20, &ss);
|
||||
|
||||
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))) {
|
||||
if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) {
|
||||
pa_log("failed to create sink.");
|
||||
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("failed to connect to server.");
|
||||
u->sink->parent.process_msg = sink_process_msg;
|
||||
u->sink->userdata = u;
|
||||
u->sink->flags = PA_SINK_LATENCY;
|
||||
|
||||
pa_sink_set_module(u->sink, m);
|
||||
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
|
||||
pa_sink_set_rtpoll(u->sink, u->rtpoll);
|
||||
|
||||
if (!(espeaker = getenv("ESPEAKER")))
|
||||
espeaker = ESD_UNIX_SOCKET_NAME;
|
||||
|
||||
if (!(u->client = pa_socket_client_new_string(u->core->mainloop, p = pa_modargs_get_value(ma, "server", espeaker), ESD_DEFAULT_PORT))) {
|
||||
pa_log("Failed to connect to server.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Esound sink '%s'", p));
|
||||
pa_xfree(t);
|
||||
|
||||
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("failed to load cookie");
|
||||
pa_log("Failed to load cookie");
|
||||
goto fail;
|
||||
}
|
||||
*(int32_t*) ((uint8_t*) u->write_data + ESD_KEY_LEN) = ESD_ENDIAN_KEY;
|
||||
|
|
@ -381,19 +589,12 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
/* 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);
|
||||
pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Esound sink '%s'", p));
|
||||
pa_xfree(t);
|
||||
|
||||
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);
|
||||
if (!(u->thread = pa_thread_new(thread_func, u))) {
|
||||
pa_log("Failed to create thread.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pa_sink_put(u->sink);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
|
||||
|
|
@ -403,20 +604,39 @@ fail:
|
|||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
pa__done(c, m);
|
||||
pa__done(m);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
assert(c && m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
||||
u->module = NULL;
|
||||
cancel(u);
|
||||
if (u->sink)
|
||||
pa_sink_unlink(u->sink);
|
||||
|
||||
if (u->thread) {
|
||||
pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
|
||||
pa_thread_free(u->thread);
|
||||
}
|
||||
|
||||
pa_thread_mq_done(&u->thread_mq);
|
||||
|
||||
if (u->sink)
|
||||
pa_sink_unref(u->sink);
|
||||
|
||||
if (u->io)
|
||||
pa_iochannel_free(u->io);
|
||||
|
||||
if (u->rtpoll_item)
|
||||
pa_rtpoll_item_free(u->rtpoll_item);
|
||||
|
||||
if (u->rtpoll)
|
||||
pa_rtpoll_free(u->rtpoll);
|
||||
|
||||
if (u->memchunk.memblock)
|
||||
pa_memblock_unref(u->memchunk.memblock);
|
||||
|
|
@ -427,8 +647,11 @@ void pa__done(pa_core *c, pa_module*m) {
|
|||
pa_xfree(u->read_data);
|
||||
pa_xfree(u->write_data);
|
||||
|
||||
if (u->smoother)
|
||||
pa_smoother_free(u->smoother);
|
||||
|
||||
if (u->fd >= 0)
|
||||
pa_close(u->fd);
|
||||
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -28,31 +28,34 @@
|
|||
#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 <pthread.h>
|
||||
|
||||
#include <jack/jack.h>
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/iochannel.h>
|
||||
#include <pulsecore/source.h>
|
||||
#include <pulsecore/module.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulse/mainloop-api.h>
|
||||
#include <pulsecore/thread.h>
|
||||
#include <pulsecore/thread-mq.h>
|
||||
#include <pulsecore/rtpoll.h>
|
||||
#include <pulsecore/sample-util.h>
|
||||
|
||||
#include "module-jack-source-symdef.h"
|
||||
|
||||
/* See module-jack-sink for a few comments how this module basically
|
||||
* works */
|
||||
|
||||
PA_MODULE_AUTHOR("Lennart Poettering")
|
||||
PA_MODULE_DESCRIPTION("Jack Source")
|
||||
PA_MODULE_DESCRIPTION("JACK Source")
|
||||
PA_MODULE_VERSION(PACKAGE_VERSION)
|
||||
PA_MODULE_USAGE(
|
||||
"source_name=<name of source> "
|
||||
|
|
@ -67,7 +70,6 @@ PA_MODULE_USAGE(
|
|||
struct userdata {
|
||||
pa_core *core;
|
||||
pa_module *module;
|
||||
|
||||
pa_source *source;
|
||||
|
||||
unsigned channels;
|
||||
|
|
@ -75,19 +77,15 @@ struct userdata {
|
|||
jack_port_t* port[PA_CHANNELS_MAX];
|
||||
jack_client_t *client;
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
pa_thread_mq thread_mq;
|
||||
pa_asyncmsgq *jack_msgq;
|
||||
pa_rtpoll *rtpoll;
|
||||
pa_rtpoll_item *rtpoll_item;
|
||||
|
||||
void * buffer[PA_CHANNELS_MAX];
|
||||
jack_nframes_t frames_posted;
|
||||
int quit_requested;
|
||||
pa_thread *thread;
|
||||
|
||||
int pipe_fds[2];
|
||||
int pipe_fd_type;
|
||||
pa_io_event *io_event;
|
||||
|
||||
jack_nframes_t frames_in_buffer;
|
||||
jack_nframes_t timestamp;
|
||||
jack_nframes_t saved_frame_time;
|
||||
pa_bool_t saved_frame_time_valid;
|
||||
};
|
||||
|
||||
static const char* const valid_modargs[] = {
|
||||
|
|
@ -100,141 +98,150 @@ static const char* const valid_modargs[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
static void stop_source(struct userdata *u) {
|
||||
assert (u);
|
||||
enum {
|
||||
SOURCE_MESSAGE_POST = PA_SOURCE_MESSAGE_MAX,
|
||||
SOURCE_MESSAGE_ON_SHUTDOWN
|
||||
};
|
||||
|
||||
jack_client_close(u->client);
|
||||
u->client = NULL;
|
||||
u->core->mainloop->io_free(u->io_event);
|
||||
u->io_event = NULL;
|
||||
pa_source_disconnect(u->source);
|
||||
pa_source_unref(u->source);
|
||||
u->source = NULL;
|
||||
pa_module_unload_request(u->module);
|
||||
}
|
||||
static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||
struct userdata *u = PA_SOURCE(o)->userdata;
|
||||
|
||||
static void io_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) {
|
||||
struct userdata *u = userdata;
|
||||
char x;
|
||||
switch (code) {
|
||||
|
||||
assert(m);
|
||||
assert(flags == PA_IO_EVENT_INPUT);
|
||||
assert(u);
|
||||
assert(u->pipe_fds[0] == fd);
|
||||
case SOURCE_MESSAGE_POST:
|
||||
|
||||
pa_read(fd, &x, 1, &u->pipe_fd_type);
|
||||
/* Handle the new block from the JACK thread */
|
||||
pa_assert(chunk);
|
||||
pa_assert(chunk->length > 0);
|
||||
|
||||
if (u->quit_requested) {
|
||||
stop_source(u);
|
||||
u->quit_requested = 0;
|
||||
return;
|
||||
}
|
||||
if (u->source->thread_info.state == PA_SOURCE_RUNNING)
|
||||
pa_source_post(u->source, chunk);
|
||||
|
||||
pthread_mutex_lock(&u->mutex);
|
||||
u->saved_frame_time = offset;
|
||||
u->saved_frame_time_valid = TRUE;
|
||||
|
||||
if (u->frames_posted > 0) {
|
||||
unsigned fs;
|
||||
jack_nframes_t frame_idx;
|
||||
pa_memchunk chunk;
|
||||
return 0;
|
||||
|
||||
fs = pa_frame_size(&u->source->sample_spec);
|
||||
case SOURCE_MESSAGE_ON_SHUTDOWN:
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
|
||||
return 0;
|
||||
|
||||
chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length = u->frames_posted * fs);
|
||||
chunk.index = 0;
|
||||
case PA_SOURCE_MESSAGE_GET_LATENCY: {
|
||||
jack_nframes_t l, ft, d;
|
||||
size_t n;
|
||||
|
||||
for (frame_idx = 0; frame_idx < u->frames_posted; frame_idx ++) {
|
||||
unsigned c;
|
||||
/* This is the "worst-case" latency */
|
||||
l = jack_port_get_total_latency(u->client, u->port[0]);
|
||||
|
||||
for (c = 0; c < u->channels; c++) {
|
||||
float *s = ((float*) u->buffer[c]) + frame_idx;
|
||||
float *d = ((float*) ((uint8_t*) chunk.memblock->data + chunk.index)) + (frame_idx * u->channels) + c;
|
||||
if (u->saved_frame_time_valid) {
|
||||
/* Adjust the worst case latency by the time that
|
||||
* passed since we last handed data to JACK */
|
||||
|
||||
*d = *s;
|
||||
ft = jack_frame_time(u->client);
|
||||
d = ft > u->saved_frame_time ? ft - u->saved_frame_time : 0;
|
||||
l += d;
|
||||
}
|
||||
|
||||
/* Convert it to usec */
|
||||
n = l * pa_frame_size(&u->source->sample_spec);
|
||||
*((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->source->sample_spec);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
pa_source_post(u->source, &chunk);
|
||||
pa_memblock_unref(chunk.memblock);
|
||||
|
||||
u->frames_posted = 0;
|
||||
|
||||
pthread_cond_signal(&u->cond);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&u->mutex);
|
||||
}
|
||||
|
||||
static void request_post(struct userdata *u) {
|
||||
char c = 'x';
|
||||
assert(u);
|
||||
|
||||
assert(u->pipe_fds[1] >= 0);
|
||||
pa_write(u->pipe_fds[1], &c, 1, &u->pipe_fd_type);
|
||||
}
|
||||
|
||||
static void jack_shutdown(void *arg) {
|
||||
struct userdata *u = arg;
|
||||
assert(u);
|
||||
|
||||
u->quit_requested = 1;
|
||||
request_post(u);
|
||||
return pa_source_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
static int jack_process(jack_nframes_t nframes, void *arg) {
|
||||
unsigned c;
|
||||
struct userdata *u = arg;
|
||||
assert(u);
|
||||
const void *buffer[PA_CHANNELS_MAX];
|
||||
void *p;
|
||||
jack_nframes_t frame_time;
|
||||
pa_memchunk chunk;
|
||||
|
||||
if (jack_transport_query(u->client, NULL) == JackTransportRolling) {
|
||||
unsigned c;
|
||||
pa_assert(u);
|
||||
|
||||
pthread_mutex_lock(&u->mutex);
|
||||
for (c = 0; c < u->channels; c++)
|
||||
pa_assert(buffer[c] = jack_port_get_buffer(u->port[c], nframes));
|
||||
|
||||
u->frames_posted = nframes;
|
||||
/* We interleave the data and pass it on to the other RT thread */
|
||||
|
||||
for (c = 0; c < u->channels; c++) {
|
||||
u->buffer[c] = jack_port_get_buffer(u->port[c], nframes);
|
||||
assert(u->buffer[c]);
|
||||
}
|
||||
pa_memchunk_reset(&chunk);
|
||||
chunk.length = nframes * pa_frame_size(&u->source->sample_spec);
|
||||
chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length);
|
||||
p = pa_memblock_acquire(chunk.memblock);
|
||||
pa_interleave(buffer, u->channels, p, sizeof(float), nframes);
|
||||
pa_memblock_release(chunk.memblock);
|
||||
|
||||
request_post(u);
|
||||
frame_time = jack_frame_time(u->client);
|
||||
|
||||
pthread_cond_wait(&u->cond, &u->mutex);
|
||||
pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_POST, NULL, frame_time, &chunk, NULL);
|
||||
|
||||
u->frames_in_buffer = nframes;
|
||||
u->timestamp = jack_get_current_transport_frame(u->client);
|
||||
|
||||
pthread_mutex_unlock(&u->mutex);
|
||||
}
|
||||
pa_memblock_unref(chunk.memblock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static pa_usec_t source_get_latency_cb(pa_source *s) {
|
||||
struct userdata *u;
|
||||
jack_nframes_t n, l, d;
|
||||
static void thread_func(void *userdata) {
|
||||
struct userdata *u = userdata;
|
||||
|
||||
assert(s);
|
||||
u = s->userdata;
|
||||
pa_assert(u);
|
||||
|
||||
if (jack_transport_query(u->client, NULL) != JackTransportRolling)
|
||||
return 0;
|
||||
pa_log_debug("Thread starting up");
|
||||
|
||||
n = jack_get_current_transport_frame(u->client);
|
||||
if (u->core->high_priority)
|
||||
pa_make_realtime();
|
||||
|
||||
if (n < u->timestamp)
|
||||
return 0;
|
||||
pa_thread_mq_install(&u->thread_mq);
|
||||
pa_rtpoll_install(u->rtpoll);
|
||||
|
||||
d = n - u->timestamp;
|
||||
l = jack_port_get_total_latency(u->client, u->port[0]);
|
||||
for (;;) {
|
||||
int ret;
|
||||
|
||||
return pa_bytes_to_usec((l + d) * pa_frame_size(&s->sample_spec), &s->sample_spec);
|
||||
if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
|
||||
goto fail;
|
||||
|
||||
if (ret == 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
fail:
|
||||
/* If this was no regular exit from the loop we have to continue
|
||||
* processing messages until we received PA_MESSAGE_SHUTDOWN */
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
|
||||
pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
|
||||
|
||||
finish:
|
||||
pa_log_debug("Thread shutting down");
|
||||
}
|
||||
|
||||
static void jack_error_func(const char*t) {
|
||||
pa_log_warn("JACK error >%s<", t);
|
||||
char *s;
|
||||
|
||||
s = pa_xstrndup(t, strcspn(t, "\n\r"));
|
||||
pa_log_warn("JACK error >%s<", s);
|
||||
pa_xfree(s);
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
static void jack_init(void *arg) {
|
||||
struct userdata *u = arg;
|
||||
|
||||
pa_log_info("JACK thread starting up.");
|
||||
|
||||
if (u->core->high_priority)
|
||||
pa_make_realtime();
|
||||
}
|
||||
|
||||
static void jack_shutdown(void* arg) {
|
||||
struct userdata *u = arg;
|
||||
|
||||
pa_log_info("JACK thread shutting down..");
|
||||
pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_ON_SHUTDOWN, NULL, 0, NULL, NULL);
|
||||
}
|
||||
|
||||
int pa__init(pa_module*m) {
|
||||
struct userdata *u = NULL;
|
||||
pa_sample_spec ss;
|
||||
pa_channel_map map;
|
||||
|
|
@ -247,40 +254,35 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
const char **ports = NULL, **p;
|
||||
char *t;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(m);
|
||||
|
||||
jack_set_error_function(jack_error_func);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("failed to parse module arguments.");
|
||||
pa_log("Failed to parse module arguments.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (pa_modargs_get_value_boolean(ma, "connect", &do_connect) < 0) {
|
||||
pa_log("failed to parse connect= argument.");
|
||||
pa_log("Failed to parse connect= argument.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
server_name = pa_modargs_get_value(ma, "server_name", NULL);
|
||||
client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio");
|
||||
client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio JACK Source");
|
||||
|
||||
u = pa_xnew0(struct userdata, 1);
|
||||
m->userdata = u;
|
||||
u->core = c;
|
||||
u->core = m->core;
|
||||
u->module = m;
|
||||
u->pipe_fds[0] = u->pipe_fds[1] = -1;
|
||||
u->pipe_fd_type = 0;
|
||||
m->userdata = u;
|
||||
u->saved_frame_time_valid = FALSE;
|
||||
|
||||
pthread_mutex_init(&u->mutex, NULL);
|
||||
pthread_cond_init(&u->cond, NULL);
|
||||
pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
|
||||
u->rtpoll = pa_rtpoll_new();
|
||||
pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
|
||||
|
||||
if (pipe(u->pipe_fds) < 0) {
|
||||
pa_log("pipe() failed: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pa_make_nonblock_fd(u->pipe_fds[1]);
|
||||
u->jack_msgq = pa_asyncmsgq_new(0);
|
||||
u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq);
|
||||
|
||||
if (!(u->client = jack_client_open(client_name, server_name ? JackServerName : JackNullOption, &status, server_name))) {
|
||||
pa_log("jack_client_open() failed.");
|
||||
|
|
@ -294,7 +296,7 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
channels++;
|
||||
|
||||
if (!channels)
|
||||
channels = c->default_sample_spec.channels;
|
||||
channels = m->core->default_sample_spec.channels;
|
||||
|
||||
if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || channels <= 0 || channels >= PA_CHANNELS_MAX) {
|
||||
pa_log("failed to parse channels= argument.");
|
||||
|
|
@ -302,7 +304,7 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
}
|
||||
|
||||
pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_ALSA);
|
||||
if (pa_modargs_get_channel_map(ma, &map) < 0 || map.channels != channels) {
|
||||
if (pa_modargs_get_channel_map(ma, NULL, &map) < 0 || map.channels != channels) {
|
||||
pa_log("failed to parse channel_map= argument.");
|
||||
goto fail;
|
||||
}
|
||||
|
|
@ -313,7 +315,7 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
ss.rate = jack_get_sample_rate(u->client);
|
||||
ss.format = PA_SAMPLE_FLOAT32NE;
|
||||
|
||||
assert(pa_sample_spec_valid(&ss));
|
||||
pa_assert(pa_sample_spec_valid(&ss));
|
||||
|
||||
for (i = 0; i < ss.channels; i++) {
|
||||
if (!(u->port[i] = jack_port_register(u->client, pa_channel_position_to_string(map.map[i]), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput|JackPortIsTerminal, 0))) {
|
||||
|
|
@ -322,19 +324,29 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!(u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) {
|
||||
if (!(u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) {
|
||||
pa_log("failed to create source.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
u->source->parent.process_msg = source_process_msg;
|
||||
u->source->userdata = u;
|
||||
pa_source_set_owner(u->source, m);
|
||||
u->source->flags = PA_SOURCE_LATENCY;
|
||||
|
||||
pa_source_set_module(u->source, m);
|
||||
pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
|
||||
pa_source_set_rtpoll(u->source, u->rtpoll);
|
||||
pa_source_set_description(u->source, t = pa_sprintf_malloc("Jack source (%s)", jack_get_client_name(u->client)));
|
||||
pa_xfree(t);
|
||||
u->source->get_latency = source_get_latency_cb;
|
||||
|
||||
jack_set_process_callback(u->client, jack_process, u);
|
||||
jack_on_shutdown(u->client, jack_shutdown, u);
|
||||
jack_set_thread_init_callback(u->client, jack_init, u);
|
||||
|
||||
if (!(u->thread = pa_thread_new(thread_func, u))) {
|
||||
pa_log("Failed to create thread.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (jack_activate(u->client)) {
|
||||
pa_log("jack_activate() failed");
|
||||
|
|
@ -359,7 +371,7 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
|
||||
}
|
||||
|
||||
u->io_event = c->mainloop->io_new(c->mainloop, u->pipe_fds[0], PA_IO_EVENT_INPUT, io_event_cb, u);
|
||||
pa_source_put(u->source);
|
||||
|
||||
free(ports);
|
||||
pa_modargs_free(ma);
|
||||
|
|
@ -372,14 +384,14 @@ fail:
|
|||
|
||||
free(ports);
|
||||
|
||||
pa__done(c, m);
|
||||
pa__done(m);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
assert(c && m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
|
@ -387,20 +399,27 @@ void pa__done(pa_core *c, pa_module*m) {
|
|||
if (u->client)
|
||||
jack_client_close(u->client);
|
||||
|
||||
if (u->io_event)
|
||||
c->mainloop->io_free(u->io_event);
|
||||
if (u->source)
|
||||
pa_source_unlink(u->source);
|
||||
|
||||
if (u->source) {
|
||||
pa_source_disconnect(u->source);
|
||||
pa_source_unref(u->source);
|
||||
if (u->thread) {
|
||||
pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
|
||||
pa_thread_free(u->thread);
|
||||
}
|
||||
|
||||
if (u->pipe_fds[0] >= 0)
|
||||
close(u->pipe_fds[0]);
|
||||
if (u->pipe_fds[1] >= 0)
|
||||
close(u->pipe_fds[1]);
|
||||
pa_thread_mq_done(&u->thread_mq);
|
||||
|
||||
if (u->source)
|
||||
pa_source_unref(u->source);
|
||||
|
||||
if (u->rtpoll_item)
|
||||
pa_rtpoll_item_free(u->rtpoll_item);
|
||||
|
||||
if (u->jack_msgq)
|
||||
pa_asyncmsgq_unref(u->jack_msgq);
|
||||
|
||||
if (u->rtpoll)
|
||||
pa_rtpoll_free(u->rtpoll);
|
||||
|
||||
pthread_mutex_destroy(&u->mutex);
|
||||
pthread_cond_destroy(&u->cond);
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
|
|
|||
673
src/modules/module-ladspa-sink.c
Normal file
673
src/modules/module-ladspa-sink.c
Normal file
|
|
@ -0,0 +1,673 @@
|
|||
/* $Id$ */
|
||||
|
||||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2004-2006 Lennart Poettering
|
||||
|
||||
PulseAudio 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.
|
||||
|
||||
PulseAudio 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 PulseAudio; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
USA.
|
||||
***/
|
||||
|
||||
/* TODO: Some plugins cause latency, and some even report it by using a control
|
||||
out port. We don't currently use the latency information. */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/namereg.h>
|
||||
#include <pulsecore/sink.h>
|
||||
#include <pulsecore/module.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/thread.h>
|
||||
#include <pulsecore/thread-mq.h>
|
||||
#include <pulsecore/rtpoll.h>
|
||||
#include <pulsecore/sample-util.h>
|
||||
|
||||
#include "module-ladspa-sink-symdef.h"
|
||||
#include "ladspa.h"
|
||||
|
||||
PA_MODULE_AUTHOR("Lennart Poettering")
|
||||
PA_MODULE_DESCRIPTION("Virtual LADSPA sink")
|
||||
PA_MODULE_VERSION(PACKAGE_VERSION)
|
||||
PA_MODULE_USAGE(
|
||||
"sink_name=<name for the sink> "
|
||||
"master=<name of sink to remap> "
|
||||
"format=<sample format> "
|
||||
"channels=<number of channels> "
|
||||
"rate=<sample rate> "
|
||||
"channel_map=<channel map> "
|
||||
"plugin=<ladspa plugin name> "
|
||||
"label=<ladspa plugin label> "
|
||||
"control=<comma seperated list of input control values>")
|
||||
|
||||
struct userdata {
|
||||
pa_core *core;
|
||||
pa_module *module;
|
||||
|
||||
pa_sink *sink, *master;
|
||||
pa_sink_input *sink_input;
|
||||
|
||||
const LADSPA_Descriptor *descriptor;
|
||||
unsigned channels;
|
||||
LADSPA_Handle handle[PA_CHANNELS_MAX];
|
||||
LADSPA_Data *input, *output;
|
||||
size_t block_size;
|
||||
unsigned long input_port, output_port;
|
||||
LADSPA_Data *control;
|
||||
|
||||
/* This is a dummy buffer. Every port must be connected, but we don't care
|
||||
about control out ports. We connect them all to this single buffer. */
|
||||
LADSPA_Data control_out;
|
||||
|
||||
pa_memchunk memchunk;
|
||||
};
|
||||
|
||||
static const char* const valid_modargs[] = {
|
||||
"sink_name",
|
||||
"master",
|
||||
"format",
|
||||
"channels",
|
||||
"rate",
|
||||
"channel_map",
|
||||
"plugin",
|
||||
"label",
|
||||
"control",
|
||||
NULL
|
||||
};
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||
struct userdata *u = PA_SINK(o)->userdata;
|
||||
|
||||
switch (code) {
|
||||
|
||||
case PA_SINK_MESSAGE_GET_LATENCY: {
|
||||
pa_usec_t usec = 0;
|
||||
|
||||
if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
|
||||
usec = 0;
|
||||
|
||||
*((pa_usec_t*) data) = usec + pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return pa_sink_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
/* Called from main context */
|
||||
static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_sink_assert_ref(s);
|
||||
pa_assert_se(u = s->userdata);
|
||||
|
||||
if (PA_SINK_LINKED(state) && u->sink_input && PA_SINK_INPUT_LINKED(pa_sink_input_get_state(u->sink_input)))
|
||||
pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||
struct userdata *u = PA_SINK_INPUT(o)->userdata;
|
||||
|
||||
switch (code) {
|
||||
case PA_SINK_INPUT_MESSAGE_GET_LATENCY:
|
||||
*((pa_usec_t*) data) = pa_bytes_to_usec(u->memchunk.length, &u->sink_input->sample_spec);
|
||||
|
||||
/* Fall through, the default handler will add in the extra
|
||||
* latency added by the resampler */
|
||||
break;
|
||||
}
|
||||
|
||||
return pa_sink_input_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(u = i->userdata);
|
||||
|
||||
if (!u->memchunk.memblock) {
|
||||
pa_memchunk tchunk;
|
||||
float *src, *dst;
|
||||
size_t fs;
|
||||
unsigned n, c;
|
||||
|
||||
pa_sink_render(u->sink, length, &tchunk);
|
||||
|
||||
fs = pa_frame_size(&i->sample_spec);
|
||||
n = tchunk.length / fs;
|
||||
|
||||
pa_assert(n > 0);
|
||||
|
||||
u->memchunk.memblock = pa_memblock_new(i->sink->core->mempool, tchunk.length);
|
||||
u->memchunk.index = 0;
|
||||
u->memchunk.length = tchunk.length;
|
||||
|
||||
src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index);
|
||||
dst = (float*) pa_memblock_acquire(u->memchunk.memblock);
|
||||
|
||||
for (c = 0; c < u->channels; c++) {
|
||||
unsigned j;
|
||||
float *p, *q;
|
||||
|
||||
p = src + c;
|
||||
q = u->input;
|
||||
for (j = 0; j < n; j++, p += u->channels, q++)
|
||||
*q = CLAMP(*p, -1.0, 1.0);
|
||||
|
||||
u->descriptor->run(u->handle[c], n);
|
||||
|
||||
q = u->output;
|
||||
p = dst + c;
|
||||
for (j = 0; j < n; j++, q++, p += u->channels)
|
||||
*p = CLAMP(*q, -1.0, 1.0);
|
||||
}
|
||||
|
||||
pa_memblock_release(tchunk.memblock);
|
||||
pa_memblock_release(u->memchunk.memblock);
|
||||
|
||||
pa_memblock_unref(tchunk.memblock);
|
||||
}
|
||||
|
||||
pa_assert(u->memchunk.length > 0);
|
||||
pa_assert(u->memchunk.memblock);
|
||||
|
||||
*chunk = u->memchunk;
|
||||
pa_memblock_ref(chunk->memblock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(u = i->userdata);
|
||||
pa_assert(length > 0);
|
||||
|
||||
if (u->memchunk.memblock) {
|
||||
|
||||
if (length < u->memchunk.length) {
|
||||
u->memchunk.index += length;
|
||||
u->memchunk.length -= length;
|
||||
return;
|
||||
}
|
||||
|
||||
pa_memblock_unref(u->memchunk.memblock);
|
||||
length -= u->memchunk.length;
|
||||
pa_memchunk_reset(&u->memchunk);
|
||||
}
|
||||
|
||||
if (length > 0)
|
||||
pa_sink_skip(u->sink, length);
|
||||
}
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static void sink_input_detach_cb(pa_sink_input *i) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(u = i->userdata);
|
||||
|
||||
pa_sink_detach_within_thread(u->sink);
|
||||
}
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static void sink_input_attach_cb(pa_sink_input *i) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(u = i->userdata);
|
||||
|
||||
pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq);
|
||||
pa_sink_set_rtpoll(u->sink, i->sink->rtpoll);
|
||||
|
||||
pa_sink_attach_within_thread(u->sink);
|
||||
}
|
||||
|
||||
/* Called from main context */
|
||||
static void sink_input_kill_cb(pa_sink_input *i) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(u = i->userdata);
|
||||
|
||||
pa_sink_input_unlink(u->sink_input);
|
||||
pa_sink_input_unref(u->sink_input);
|
||||
u->sink_input = NULL;
|
||||
|
||||
pa_sink_unlink(u->sink);
|
||||
pa_sink_unref(u->sink);
|
||||
u->sink = NULL;
|
||||
|
||||
pa_module_unload_request(u->module);
|
||||
}
|
||||
|
||||
int pa__init(pa_module*m) {
|
||||
struct userdata *u;
|
||||
pa_sample_spec ss;
|
||||
pa_channel_map map;
|
||||
pa_modargs *ma;
|
||||
char *t;
|
||||
pa_sink *master;
|
||||
pa_sink_input_new_data data;
|
||||
const char *plugin, *label;
|
||||
LADSPA_Descriptor_Function descriptor_func;
|
||||
const char *e, *cdata;
|
||||
const LADSPA_Descriptor *d;
|
||||
unsigned long input_port, output_port, p, j, n_control;
|
||||
unsigned c;
|
||||
pa_bool_t *use_default = NULL;
|
||||
char *default_sink_name = NULL;
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
pa_assert(sizeof(LADSPA_Data) == sizeof(float));
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("Failed to parse module arguments.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK, 1))) {
|
||||
pa_log("Master sink not found");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ss = master->sample_spec;
|
||||
ss.format = PA_SAMPLE_FLOAT32;
|
||||
map = master->channel_map;
|
||||
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
|
||||
pa_log("Invalid sample format specification or channel map");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!(plugin = pa_modargs_get_value(ma, "plugin", NULL))) {
|
||||
pa_log("Missing LADSPA plugin name");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!(label = pa_modargs_get_value(ma, "label", NULL))) {
|
||||
pa_log("Missing LADSPA plugin label");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
cdata = pa_modargs_get_value(ma, "control", NULL);
|
||||
|
||||
u = pa_xnew0(struct userdata, 1);
|
||||
u->core = m->core;
|
||||
u->module = m;
|
||||
m->userdata = u;
|
||||
u->master = master;
|
||||
pa_memchunk_reset(&u->memchunk);
|
||||
|
||||
if (!(e = getenv("LADSPA_PATH")))
|
||||
e = LADSPA_PATH;
|
||||
|
||||
/* FIXME: This is not exactly thread safe */
|
||||
t = pa_xstrdup(lt_dlgetsearchpath());
|
||||
lt_dlsetsearchpath(e);
|
||||
m->dl = lt_dlopenext(plugin);
|
||||
lt_dlsetsearchpath(t);
|
||||
pa_xfree(t);
|
||||
|
||||
if (!m->dl) {
|
||||
pa_log("Failed to load LADSPA plugin: %s", lt_dlerror());
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!(descriptor_func = (LADSPA_Descriptor_Function) lt_dlsym(m->dl, "ladspa_descriptor"))) {
|
||||
pa_log("LADSPA module lacks ladspa_descriptor() symbol.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (j = 0;; j++) {
|
||||
|
||||
if (!(d = descriptor_func(j))) {
|
||||
pa_log("Failed to find plugin label '%s' in plugin '%s'.", plugin, label);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (strcmp(d->Label, label) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
u->descriptor = d;
|
||||
|
||||
pa_log_debug("Module: %s", plugin);
|
||||
pa_log_debug("Label: %s", d->Label);
|
||||
pa_log_debug("Unique ID: %lu", d->UniqueID);
|
||||
pa_log_debug("Name: %s", d->Name);
|
||||
pa_log_debug("Maker: %s", d->Maker);
|
||||
pa_log_debug("Copyright: %s", d->Copyright);
|
||||
|
||||
input_port = output_port = (unsigned long) -1;
|
||||
n_control = 0;
|
||||
|
||||
for (p = 0; p < d->PortCount; p++) {
|
||||
|
||||
if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) {
|
||||
|
||||
if (strcmp(d->PortNames[p], "Input") == 0) {
|
||||
pa_assert(input_port == (unsigned long) -1);
|
||||
input_port = p;
|
||||
} else {
|
||||
pa_log("Found audio input port on plugin we cannot handle: %s", d->PortNames[p]);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
} else if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) {
|
||||
|
||||
if (strcmp(d->PortNames[p], "Output") == 0) {
|
||||
pa_assert(output_port == (unsigned long) -1);
|
||||
output_port = p;
|
||||
} else {
|
||||
pa_log("Found audio output port on plugin we cannot handle: %s", d->PortNames[p]);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
} else if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p]))
|
||||
n_control++;
|
||||
else {
|
||||
pa_assert(LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p]));
|
||||
pa_log_info("Ignored port \"%s\", because we ignore all control out ports.", d->PortNames[p]);
|
||||
}
|
||||
}
|
||||
|
||||
if ((input_port == (unsigned long) -1) || (output_port == (unsigned long) -1)) {
|
||||
pa_log("Failed to identify input and output ports. "
|
||||
"Right now this module can only deal with plugins which provide an 'Input' and an 'Output' audio port. "
|
||||
"Patches welcome!");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
u->block_size = pa_frame_align(pa_mempool_block_size_max(m->core->mempool), &ss);
|
||||
|
||||
u->input = (LADSPA_Data*) pa_xnew(uint8_t, u->block_size);
|
||||
if (LADSPA_IS_INPLACE_BROKEN(d->Properties))
|
||||
u->output = (LADSPA_Data*) pa_xnew(uint8_t, u->block_size);
|
||||
else
|
||||
u->output = u->input;
|
||||
|
||||
u->channels = ss.channels;
|
||||
|
||||
for (c = 0; c < ss.channels; c++) {
|
||||
if (!(u->handle[c] = d->instantiate(d, ss.rate))) {
|
||||
pa_log("Failed to instantiate plugin %s with label %s for channel %i", plugin, d->Label, c);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
d->connect_port(u->handle[c], input_port, u->input);
|
||||
d->connect_port(u->handle[c], output_port, u->output);
|
||||
}
|
||||
|
||||
if (!cdata && n_control > 0) {
|
||||
pa_log("This plugin requires specification of %lu control parameters.", n_control);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (n_control > 0) {
|
||||
const char *state = NULL;
|
||||
char *k;
|
||||
unsigned long h;
|
||||
|
||||
u->control = pa_xnew(LADSPA_Data, n_control);
|
||||
use_default = pa_xnew(pa_bool_t, n_control);
|
||||
p = 0;
|
||||
|
||||
while ((k = pa_split(cdata, ",", &state))) {
|
||||
float f;
|
||||
|
||||
if (*k == 0) {
|
||||
use_default[p++] = TRUE;
|
||||
pa_xfree(k);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pa_atof(k, &f) < 0) {
|
||||
pa_log("Failed to parse control value '%s'", k);
|
||||
pa_xfree(k);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pa_xfree(k);
|
||||
|
||||
if (p >= n_control) {
|
||||
pa_log("Too many control values passed, %lu expected.", n_control);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
use_default[p] = FALSE;
|
||||
u->control[p++] = f;
|
||||
}
|
||||
|
||||
if (p < n_control) {
|
||||
pa_log("Not enough control values passed, %lu expected, %lu passed.", n_control, p);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
h = 0;
|
||||
for (p = 0; p < d->PortCount; p++) {
|
||||
LADSPA_PortRangeHintDescriptor hint = d->PortRangeHints[p].HintDescriptor;
|
||||
|
||||
if (!LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p]))
|
||||
continue;
|
||||
|
||||
if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) {
|
||||
for (c = 0; c < ss.channels; c++)
|
||||
d->connect_port(u->handle[c], p, &u->control_out);
|
||||
continue;
|
||||
}
|
||||
|
||||
pa_assert(h < n_control);
|
||||
|
||||
if (use_default[h]) {
|
||||
LADSPA_Data lower, upper;
|
||||
|
||||
if (!LADSPA_IS_HINT_HAS_DEFAULT(hint)) {
|
||||
pa_log("Control port value left empty but plugin defines no default.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
lower = d->PortRangeHints[p].LowerBound;
|
||||
upper = d->PortRangeHints[p].UpperBound;
|
||||
|
||||
if (LADSPA_IS_HINT_SAMPLE_RATE(hint)) {
|
||||
lower *= ss.rate;
|
||||
upper *= ss.rate;
|
||||
}
|
||||
|
||||
switch (hint & LADSPA_HINT_DEFAULT_MASK) {
|
||||
|
||||
case LADSPA_HINT_DEFAULT_MINIMUM:
|
||||
u->control[h] = lower;
|
||||
break;
|
||||
|
||||
case LADSPA_HINT_DEFAULT_MAXIMUM:
|
||||
u->control[h] = upper;
|
||||
break;
|
||||
|
||||
case LADSPA_HINT_DEFAULT_LOW:
|
||||
if (LADSPA_IS_HINT_LOGARITHMIC(hint))
|
||||
u->control[h] = exp(log(lower) * 0.75 + log(upper) * 0.25);
|
||||
else
|
||||
u->control[h] = lower * 0.75 + upper * 0.25;
|
||||
break;
|
||||
|
||||
case LADSPA_HINT_DEFAULT_MIDDLE:
|
||||
if (LADSPA_IS_HINT_LOGARITHMIC(hint))
|
||||
u->control[h] = exp(log(lower) * 0.5 + log(upper) * 0.5);
|
||||
else
|
||||
u->control[h] = lower * 0.5 + upper * 0.5;
|
||||
break;
|
||||
|
||||
case LADSPA_HINT_DEFAULT_HIGH:
|
||||
if (LADSPA_IS_HINT_LOGARITHMIC(hint))
|
||||
u->control[h] = exp(log(lower) * 0.25 + log(upper) * 0.75);
|
||||
else
|
||||
u->control[h] = lower * 0.25 + upper * 0.75;
|
||||
break;
|
||||
|
||||
case LADSPA_HINT_DEFAULT_0:
|
||||
u->control[h] = 0;
|
||||
break;
|
||||
|
||||
case LADSPA_HINT_DEFAULT_1:
|
||||
u->control[h] = 1;
|
||||
break;
|
||||
|
||||
case LADSPA_HINT_DEFAULT_100:
|
||||
u->control[h] = 100;
|
||||
break;
|
||||
|
||||
case LADSPA_HINT_DEFAULT_440:
|
||||
u->control[h] = 440;
|
||||
break;
|
||||
|
||||
default:
|
||||
pa_assert_not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
if (LADSPA_IS_HINT_INTEGER(hint))
|
||||
u->control[h] = roundf(u->control[h]);
|
||||
|
||||
pa_log_debug("Binding %f to port %s", u->control[h], d->PortNames[p]);
|
||||
|
||||
for (c = 0; c < ss.channels; c++)
|
||||
d->connect_port(u->handle[c], p, &u->control[h]);
|
||||
|
||||
h++;
|
||||
}
|
||||
|
||||
pa_assert(h == n_control);
|
||||
}
|
||||
|
||||
if (d->activate)
|
||||
for (c = 0; c < u->channels; c++)
|
||||
d->activate(u->handle[c]);
|
||||
|
||||
default_sink_name = pa_sprintf_malloc("%s.ladspa", master->name);
|
||||
|
||||
/* Create sink */
|
||||
if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", default_sink_name), 0, &ss, &map))) {
|
||||
pa_log("Failed to create sink.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
u->sink->parent.process_msg = sink_process_msg;
|
||||
u->sink->set_state = sink_set_state;
|
||||
u->sink->userdata = u;
|
||||
u->sink->flags = PA_SINK_LATENCY;
|
||||
|
||||
pa_sink_set_module(u->sink, m);
|
||||
pa_sink_set_description(u->sink, t = pa_sprintf_malloc("LADSPA plugin '%s' on '%s'", label, master->description));
|
||||
pa_xfree(t);
|
||||
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
|
||||
pa_sink_set_rtpoll(u->sink, master->rtpoll);
|
||||
|
||||
/* Create sink input */
|
||||
pa_sink_input_new_data_init(&data);
|
||||
data.sink = u->master;
|
||||
data.driver = __FILE__;
|
||||
data.name = "LADSPA Stream";
|
||||
pa_sink_input_new_data_set_sample_spec(&data, &ss);
|
||||
pa_sink_input_new_data_set_channel_map(&data, &map);
|
||||
data.module = m;
|
||||
|
||||
if (!(u->sink_input = pa_sink_input_new(m->core, &data, PA_SINK_INPUT_DONT_MOVE)))
|
||||
goto fail;
|
||||
|
||||
u->sink_input->parent.process_msg = sink_input_process_msg;
|
||||
u->sink_input->peek = sink_input_peek_cb;
|
||||
u->sink_input->drop = sink_input_drop_cb;
|
||||
u->sink_input->kill = sink_input_kill_cb;
|
||||
u->sink_input->attach = sink_input_attach_cb;
|
||||
u->sink_input->detach = sink_input_detach_cb;
|
||||
u->sink_input->userdata = u;
|
||||
|
||||
pa_sink_put(u->sink);
|
||||
pa_sink_input_put(u->sink_input);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
|
||||
pa_xfree(use_default);
|
||||
pa_xfree(default_sink_name);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
pa_xfree(use_default);
|
||||
pa_xfree(default_sink_name);
|
||||
|
||||
pa__done(m);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
unsigned c;
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
||||
if (u->sink_input) {
|
||||
pa_sink_input_unlink(u->sink_input);
|
||||
pa_sink_input_unref(u->sink_input);
|
||||
}
|
||||
|
||||
if (u->sink) {
|
||||
pa_sink_unlink(u->sink);
|
||||
pa_sink_unref(u->sink);
|
||||
}
|
||||
|
||||
if (u->memchunk.memblock)
|
||||
pa_memblock_unref(u->memchunk.memblock);
|
||||
|
||||
for (c = 0; c < u->channels; c++)
|
||||
if (u->handle[c]) {
|
||||
if (u->descriptor->deactivate)
|
||||
u->descriptor->deactivate(u->handle[c]);
|
||||
u->descriptor->cleanup(u->handle[c]);
|
||||
}
|
||||
|
||||
if (u->output != u->input)
|
||||
pa_xfree(u->output);
|
||||
|
||||
pa_xfree(u->input);
|
||||
|
||||
pa_xfree(u->control);
|
||||
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
|
@ -26,12 +26,12 @@
|
|||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <lirc/lirc_client.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <lirc/lirc_client.h>
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
|
||||
#include <pulsecore/module.h>
|
||||
|
|
@ -39,6 +39,7 @@
|
|||
#include <pulsecore/namereg.h>
|
||||
#include <pulsecore/sink.h>
|
||||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/macro.h>
|
||||
|
||||
#include "module-lirc-symdef.h"
|
||||
|
||||
|
|
@ -68,11 +69,12 @@ 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);
|
||||
|
||||
pa_assert(io);
|
||||
pa_assert(u);
|
||||
|
||||
if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
|
||||
pa_log("lost connection to LIRC daemon.");
|
||||
pa_log("Lost connection to LIRC daemon.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -86,7 +88,7 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC
|
|||
|
||||
c = pa_xstrdup(code);
|
||||
c[strcspn(c, "\n\r")] = 0;
|
||||
pa_log_debug("raw IR code '%s'", c);
|
||||
pa_log_debug("Raw IR code '%s'", c);
|
||||
pa_xfree(c);
|
||||
|
||||
while (lirc_code2char(u->config, code, &name) == 0 && name) {
|
||||
|
|
@ -99,7 +101,7 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC
|
|||
MUTE_TOGGLE
|
||||
} volchange = INVALID;
|
||||
|
||||
pa_log_info("translated IR code '%s'", name);
|
||||
pa_log_info("Translated IR code '%s'", name);
|
||||
|
||||
if (strcasecmp(name, "volume-up") == 0)
|
||||
volchange = UP;
|
||||
|
|
@ -113,15 +115,15 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC
|
|||
volchange = RESET;
|
||||
|
||||
if (volchange == INVALID)
|
||||
pa_log_warn("recieved unknown IR code '%s'", name);
|
||||
pa_log_warn("Recieved unknown IR code '%s'", name);
|
||||
else {
|
||||
pa_sink *s;
|
||||
|
||||
if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1)))
|
||||
pa_log("failed to get sink '%s'", u->sink_name);
|
||||
pa_log("Failed to get sink '%s'", u->sink_name);
|
||||
else {
|
||||
int i;
|
||||
pa_cvolume cv = *pa_sink_get_volume(s, PA_MIXER_HARDWARE);
|
||||
pa_cvolume cv = *pa_sink_get_volume(s);
|
||||
|
||||
#define DELTA (PA_VOLUME_NORM/20)
|
||||
|
||||
|
|
@ -134,7 +136,7 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC
|
|||
cv.values[i] = PA_VOLUME_NORM;
|
||||
}
|
||||
|
||||
pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv);
|
||||
pa_sink_set_volume(s, &cv);
|
||||
break;
|
||||
|
||||
case DOWN:
|
||||
|
|
@ -145,20 +147,20 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC
|
|||
cv.values[i] = PA_VOLUME_MUTED;
|
||||
}
|
||||
|
||||
pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv);
|
||||
pa_sink_set_volume(s, &cv);
|
||||
break;
|
||||
|
||||
case MUTE:
|
||||
pa_sink_set_mute(s, PA_MIXER_HARDWARE, 0);
|
||||
pa_sink_set_mute(s, 0);
|
||||
break;
|
||||
|
||||
case RESET:
|
||||
pa_sink_set_mute(s, PA_MIXER_HARDWARE, 1);
|
||||
pa_sink_set_mute(s, 1);
|
||||
break;
|
||||
|
||||
case MUTE_TOGGLE:
|
||||
|
||||
pa_sink_set_mute(s, PA_MIXER_HARDWARE, !pa_sink_get_mute(s, PA_MIXER_HARDWARE));
|
||||
pa_sink_set_mute(s, !pa_sink_get_mute(s));
|
||||
break;
|
||||
|
||||
case INVALID:
|
||||
|
|
@ -179,13 +181,14 @@ fail:
|
|||
|
||||
pa_module_unload_request(u->module);
|
||||
|
||||
free(code);
|
||||
pa_xfree(code);
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
pa_modargs *ma = NULL;
|
||||
struct userdata *u;
|
||||
assert(c && m);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (lirc_in_use) {
|
||||
pa_log("module-lirc may no be loaded twice.");
|
||||
|
|
@ -197,7 +200,7 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
m->userdata = u = pa_xmalloc(sizeof(struct userdata));
|
||||
m->userdata = u = pa_xnew(struct userdata, 1);
|
||||
u->module = m;
|
||||
u->io = NULL;
|
||||
u->config = NULL;
|
||||
|
|
@ -215,7 +218,7 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
u->io = c->mainloop->io_new(c->mainloop, u->lirc_fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u);
|
||||
u->io = m->core->mainloop->io_new(m->core->mainloop, u->lirc_fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u);
|
||||
|
||||
lirc_in_use = 1;
|
||||
|
||||
|
|
@ -228,14 +231,13 @@ fail:
|
|||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
pa__done(c, m);
|
||||
pa__done(m);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@
|
|||
#endif
|
||||
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
|
|
@ -80,6 +79,8 @@ static int load_rules(struct userdata *u, const char *filename) {
|
|||
struct rule *end = NULL;
|
||||
char *fn = NULL;
|
||||
|
||||
pa_assert(u);
|
||||
|
||||
f = filename ?
|
||||
fopen(fn = pa_xstrdup(filename), "r") :
|
||||
pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn, "r");
|
||||
|
|
@ -132,7 +133,7 @@ static int load_rules(struct userdata *u, const char *filename) {
|
|||
goto finish;
|
||||
}
|
||||
|
||||
rule = pa_xmalloc(sizeof(struct rule));
|
||||
rule = pa_xnew(struct rule, 1);
|
||||
rule->regex = regex;
|
||||
rule->volume = volume;
|
||||
rule->next = NULL;
|
||||
|
|
@ -164,7 +165,9 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v
|
|||
struct userdata *u = userdata;
|
||||
pa_sink_input *si;
|
||||
struct rule *r;
|
||||
assert(c && u);
|
||||
|
||||
pa_assert(c);
|
||||
pa_assert(u);
|
||||
|
||||
if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW))
|
||||
return;
|
||||
|
|
@ -179,23 +182,24 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v
|
|||
if (!regexec(&r->regex, si->name, 0, NULL, 0)) {
|
||||
pa_cvolume cv;
|
||||
pa_log_debug("changing volume of sink input '%s' to 0x%03x", si->name, r->volume);
|
||||
pa_cvolume_set(&cv, r->volume, si->sample_spec.channels);
|
||||
pa_cvolume_set(&cv, si->sample_spec.channels, r->volume);
|
||||
pa_sink_input_set_volume(si, &cv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
pa_modargs *ma = NULL;
|
||||
struct userdata *u;
|
||||
assert(c && m);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("Failed to parse module arguments");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
u = pa_xmalloc(sizeof(struct userdata));
|
||||
u = pa_xnew(struct userdata, 1);
|
||||
u->rules = NULL;
|
||||
u->subscription = NULL;
|
||||
m->userdata = u;
|
||||
|
|
@ -203,23 +207,24 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
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);
|
||||
u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
pa__done(c, m);
|
||||
pa__done(m);
|
||||
|
||||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata* u;
|
||||
struct rule *r, *n;
|
||||
assert(c && m);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@
|
|||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -80,11 +79,12 @@ struct userdata {
|
|||
|
||||
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);
|
||||
|
||||
pa_assert(io);
|
||||
pa_assert(u);
|
||||
|
||||
if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) {
|
||||
pa_log("lost connection to evdev device.");
|
||||
pa_log("Lost connection to evdev device.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -92,14 +92,14 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC
|
|||
struct input_event ev;
|
||||
|
||||
if (pa_loop_read(u->fd, &ev, sizeof(ev), &u->fd_type) <= 0) {
|
||||
pa_log("failed to read from event device: %s", pa_cstrerror(errno));
|
||||
pa_log("Failed to read from event device: %s", pa_cstrerror(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("key code=%u, value=%u", ev.code, ev.value);
|
||||
pa_log_debug("Key code=%u, value=%u", ev.code, ev.value);
|
||||
|
||||
switch (ev.code) {
|
||||
case KEY_VOLUMEDOWN: volchange = DOWN; break;
|
||||
|
|
@ -111,10 +111,10 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC
|
|||
pa_sink *s;
|
||||
|
||||
if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1)))
|
||||
pa_log("failed to get sink '%s'", u->sink_name);
|
||||
pa_log("Failed to get sink '%s'", u->sink_name);
|
||||
else {
|
||||
int i;
|
||||
pa_cvolume cv = *pa_sink_get_volume(s, PA_MIXER_HARDWARE);
|
||||
pa_cvolume cv = *pa_sink_get_volume(s);
|
||||
|
||||
#define DELTA (PA_VOLUME_NORM/20)
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC
|
|||
cv.values[i] = PA_VOLUME_NORM;
|
||||
}
|
||||
|
||||
pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv);
|
||||
pa_sink_set_volume(s, &cv);
|
||||
break;
|
||||
|
||||
case DOWN:
|
||||
|
|
@ -138,12 +138,12 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC
|
|||
cv.values[i] = PA_VOLUME_MUTED;
|
||||
}
|
||||
|
||||
pa_sink_set_volume(s, PA_MIXER_HARDWARE, &cv);
|
||||
pa_sink_set_volume(s, &cv);
|
||||
break;
|
||||
|
||||
case MUTE_TOGGLE:
|
||||
|
||||
pa_sink_set_mute(s, PA_MIXER_HARDWARE, !pa_sink_get_mute(s, PA_MIXER_HARDWARE));
|
||||
pa_sink_set_mute(s, !pa_sink_get_mute(s));
|
||||
break;
|
||||
|
||||
case INVALID:
|
||||
|
|
@ -165,21 +165,23 @@ fail:
|
|||
|
||||
#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8)))
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(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);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("Failed to parse module arguments");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
m->userdata = u = pa_xmalloc(sizeof(struct userdata));
|
||||
m->userdata = u = pa_xnew(struct userdata,1);
|
||||
u->module = m;
|
||||
u->io = NULL;
|
||||
u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
|
||||
|
|
@ -221,11 +223,11 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
}
|
||||
|
||||
if (!test_bit(EV_KEY, evtype_bitmask)) {
|
||||
pa_log("device has no keys.");
|
||||
pa_log("Device has no keys.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
u->io = c->mainloop->io_new(c->mainloop, u->fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u);
|
||||
u->io = m->core->mainloop->io_new(m->core->mainloop, u->fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
|
||||
|
|
@ -236,14 +238,14 @@ fail:
|
|||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
pa__done(c, m);
|
||||
pa__done(m);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
assert(c);
|
||||
assert(m);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
|
@ -252,7 +254,7 @@ void pa__done(pa_core *c, pa_module*m) {
|
|||
m->core->mainloop->io_free(u->io);
|
||||
|
||||
if (u->fd >= 0)
|
||||
close(u->fd);
|
||||
pa_assert_se(pa_close(u->fd) == 0);
|
||||
|
||||
pa_xfree(u->sink_name);
|
||||
pa_xfree(u);
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@
|
|||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <pulsecore/module.h>
|
||||
#include <pulsecore/macro.h>
|
||||
#include <pulsecore/iochannel.h>
|
||||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/protocol-native.h>
|
||||
|
|
@ -48,25 +48,26 @@ static const char* const valid_modargs[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
pa_iochannel *io;
|
||||
pa_modargs *ma;
|
||||
int fd, r = -1;
|
||||
assert(c && m);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("failed to parse module arguments.");
|
||||
pa_log("Failed to parse module arguments.");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (pa_modargs_get_value_s32(ma, "fd", &fd) < 0) {
|
||||
pa_log("invalid file descriptor.");
|
||||
pa_log("Invalid file descriptor.");
|
||||
goto finish;
|
||||
}
|
||||
|
||||
io = pa_iochannel_new(c->mainloop, fd, fd);
|
||||
io = pa_iochannel_new(m->core->mainloop, fd, fd);
|
||||
|
||||
if (!(m->userdata = pa_protocol_native_new_iochannel(c, io, m, ma))) {
|
||||
if (!(m->userdata = pa_protocol_native_new_iochannel(m->core, io, m, ma))) {
|
||||
pa_iochannel_free(io);
|
||||
goto finish;
|
||||
}
|
||||
|
|
@ -80,8 +81,8 @@ finish:
|
|||
return r;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
assert(c && m);
|
||||
void pa__done(pa_module*m) {
|
||||
pa_assert(m);
|
||||
|
||||
pa_protocol_native_free(m->userdata);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@
|
|||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
|
|
@ -38,12 +37,17 @@
|
|||
#include <pulse/timeval.h>
|
||||
#include <pulse/xmalloc.h>
|
||||
|
||||
#include <pulsecore/iochannel.h>
|
||||
#include <pulsecore/macro.h>
|
||||
#include <pulsecore/sink.h>
|
||||
#include <pulsecore/module.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/thread.h>
|
||||
#include <pulsecore/thread-mq.h>
|
||||
#include <pulsecore/rtpoll.h>
|
||||
#include <pulsecore/rtclock.h>
|
||||
|
||||
#include "module-null-sink-symdef.h"
|
||||
|
||||
|
|
@ -64,11 +68,14 @@ struct userdata {
|
|||
pa_core *core;
|
||||
pa_module *module;
|
||||
pa_sink *sink;
|
||||
pa_time_event *time_event;
|
||||
|
||||
pa_thread *thread;
|
||||
pa_thread_mq thread_mq;
|
||||
pa_rtpoll *rtpoll;
|
||||
|
||||
size_t block_size;
|
||||
|
||||
uint64_t n_bytes;
|
||||
struct timeval start_time;
|
||||
struct timeval timestamp;
|
||||
};
|
||||
|
||||
static const char* const valid_modargs[] = {
|
||||
|
|
@ -81,78 +88,132 @@ static const char* const valid_modargs[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
static void time_callback(pa_mainloop_api *m, pa_time_event*e, const struct timeval *tv, void *userdata) {
|
||||
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||
struct userdata *u = PA_SINK(o)->userdata;
|
||||
|
||||
switch (code) {
|
||||
case PA_SINK_MESSAGE_SET_STATE:
|
||||
|
||||
if (PA_PTR_TO_UINT(data) == PA_SINK_RUNNING)
|
||||
pa_rtclock_get(&u->timestamp);
|
||||
|
||||
break;
|
||||
|
||||
case PA_SINK_MESSAGE_GET_LATENCY: {
|
||||
struct timeval now;
|
||||
|
||||
pa_rtclock_get(&now);
|
||||
|
||||
if (pa_timeval_cmp(&u->timestamp, &now) > 0)
|
||||
*((pa_usec_t*) data) = 0;
|
||||
else
|
||||
*((pa_usec_t*) data) = pa_timeval_diff(&u->timestamp, &now);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return pa_sink_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
static void thread_func(void *userdata) {
|
||||
struct userdata *u = userdata;
|
||||
pa_memchunk chunk;
|
||||
struct timeval ntv = *tv;
|
||||
size_t l;
|
||||
|
||||
assert(u);
|
||||
pa_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_log_debug("Thread starting up");
|
||||
|
||||
pa_timeval_add(&ntv, pa_bytes_to_usec(l, &u->sink->sample_spec));
|
||||
m->time_restart(e, &ntv);
|
||||
pa_thread_mq_install(&u->thread_mq);
|
||||
pa_rtpoll_install(u->rtpoll);
|
||||
|
||||
u->n_bytes += l;
|
||||
pa_rtclock_get(&u->timestamp);
|
||||
|
||||
for (;;) {
|
||||
int ret;
|
||||
|
||||
/* Render some data and drop it immediately */
|
||||
if (u->sink->thread_info.state == PA_SINK_RUNNING) {
|
||||
struct timeval now;
|
||||
|
||||
pa_rtclock_get(&now);
|
||||
|
||||
if (pa_timeval_cmp(&u->timestamp, &now) <= 0) {
|
||||
pa_sink_skip(u->sink, u->block_size);
|
||||
pa_timeval_add(&u->timestamp, pa_bytes_to_usec(u->block_size, &u->sink->sample_spec));
|
||||
}
|
||||
|
||||
pa_rtpoll_set_timer_absolute(u->rtpoll, &u->timestamp);
|
||||
} else
|
||||
pa_rtpoll_set_timer_disabled(u->rtpoll);
|
||||
|
||||
/* Hmm, nothing to do. Let's sleep */
|
||||
if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0)
|
||||
goto fail;
|
||||
|
||||
if (ret == 0)
|
||||
goto finish;
|
||||
}
|
||||
|
||||
fail:
|
||||
/* If this was no regular exit from the loop we have to continue
|
||||
* processing messages until we received PA_MESSAGE_SHUTDOWN */
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
|
||||
pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
|
||||
|
||||
finish:
|
||||
pa_log_debug("Thread shutting down");
|
||||
}
|
||||
|
||||
static pa_usec_t get_latency(pa_sink *s) {
|
||||
struct userdata *u = s->userdata;
|
||||
pa_usec_t a, b;
|
||||
struct timeval now;
|
||||
|
||||
a = pa_timeval_diff(pa_gettimeofday(&now), &u->start_time);
|
||||
b = pa_bytes_to_usec(u->n_bytes, &s->sample_spec);
|
||||
|
||||
return b > a ? b - a : 0;
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
struct userdata *u = NULL;
|
||||
pa_sample_spec ss;
|
||||
pa_channel_map map;
|
||||
pa_modargs *ma = NULL;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("failed to parse module arguments.");
|
||||
pa_log("Failed to parse module arguments.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ss = c->default_sample_spec;
|
||||
ss = m->core->default_sample_spec;
|
||||
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
|
||||
pa_log("invalid sample format specification or channel map.");
|
||||
pa_log("Invalid sample format specification or channel map");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
u = pa_xnew0(struct userdata, 1);
|
||||
u->core = c;
|
||||
u->core = m->core;
|
||||
u->module = m;
|
||||
m->userdata = u;
|
||||
pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
|
||||
u->rtpoll = pa_rtpoll_new();
|
||||
pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
|
||||
|
||||
if (!(u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) {
|
||||
pa_log("failed to create sink.");
|
||||
if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) {
|
||||
pa_log("Failed to create sink.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
u->sink->get_latency = get_latency;
|
||||
u->sink->parent.process_msg = sink_process_msg;
|
||||
u->sink->userdata = u;
|
||||
pa_sink_set_owner(u->sink, m);
|
||||
u->sink->flags = PA_SINK_LATENCY;
|
||||
|
||||
pa_sink_set_module(u->sink, m);
|
||||
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
|
||||
pa_sink_set_rtpoll(u->sink, u->rtpoll);
|
||||
pa_sink_set_description(u->sink, pa_modargs_get_value(ma, "description", "NULL sink"));
|
||||
|
||||
u->n_bytes = 0;
|
||||
pa_gettimeofday(&u->start_time);
|
||||
u->block_size = pa_bytes_per_second(&ss) / 20; /* 50 ms */
|
||||
if (u->block_size <= 0)
|
||||
u->block_size = pa_frame_size(&ss);
|
||||
|
||||
u->time_event = c->mainloop->time_new(c->mainloop, &u->start_time, time_callback, u);
|
||||
if (!(u->thread = pa_thread_new(thread_func, u))) {
|
||||
pa_log("Failed to create thread.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
u->block_size = pa_bytes_per_second(&ss) / 10;
|
||||
pa_sink_put(u->sink);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
|
||||
|
|
@ -162,22 +223,34 @@ fail:
|
|||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
pa__done(c, m);
|
||||
pa__done(m);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
assert(c && m);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
||||
pa_sink_disconnect(u->sink);
|
||||
pa_sink_unref(u->sink);
|
||||
if (u->sink)
|
||||
pa_sink_unlink(u->sink);
|
||||
|
||||
u->core->mainloop->time_free(u->time_event);
|
||||
if (u->thread) {
|
||||
pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
|
||||
pa_thread_free(u->thread);
|
||||
}
|
||||
|
||||
pa_thread_mq_done(&u->thread_mq);
|
||||
|
||||
if (u->sink)
|
||||
pa_sink_unref(u->sink);
|
||||
|
||||
if (u->rtpoll)
|
||||
pa_rtpoll_free(u->rtpoll);
|
||||
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,637 +0,0 @@
|
|||
/* $Id$ */
|
||||
|
||||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2004-2006 Lennart Poettering
|
||||
Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
|
||||
|
||||
PulseAudio 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.
|
||||
|
||||
PulseAudio 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 PulseAudio; 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>
|
||||
|
||||
#ifdef HAVE_SYS_MMAN_H
|
||||
#include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
#include <pulse/util.h>
|
||||
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/iochannel.h>
|
||||
#include <pulsecore/sink.h>
|
||||
#include <pulsecore/source.h>
|
||||
#include <pulsecore/module.h>
|
||||
#include <pulsecore/sample-util.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/log.h>
|
||||
|
||||
#include "oss-util.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> "
|
||||
"channel_map=<channel map>")
|
||||
|
||||
struct userdata {
|
||||
pa_sink *sink;
|
||||
pa_source *source;
|
||||
pa_core *core;
|
||||
pa_sample_spec sample_spec;
|
||||
|
||||
size_t in_fragment_size, out_fragment_size;
|
||||
unsigned in_fragments, out_fragments;
|
||||
unsigned out_blocks_saved, in_blocks_saved;
|
||||
|
||||
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",
|
||||
"channel_map",
|
||||
NULL
|
||||
};
|
||||
|
||||
#define DEFAULT_DEVICE "/dev/dsp"
|
||||
#define DEFAULT_NFRAGS 12
|
||||
#define DEFAULT_FRAGSIZE 1024
|
||||
|
||||
static void update_usage(struct userdata *u) {
|
||||
pa_module_set_used(u->module,
|
||||
(u->sink ? pa_sink_used_by(u->sink) : 0) +
|
||||
(u->source ? pa_source_used_by(u->source) : 0));
|
||||
}
|
||||
|
||||
static void clear_up(struct userdata *u) {
|
||||
assert(u);
|
||||
|
||||
if (u->sink) {
|
||||
pa_sink_disconnect(u->sink);
|
||||
pa_sink_unref(u->sink);
|
||||
u->sink = NULL;
|
||||
}
|
||||
|
||||
if (u->source) {
|
||||
pa_source_disconnect(u->source);
|
||||
pa_source_unref(u->source);
|
||||
u->source = NULL;
|
||||
}
|
||||
|
||||
if (u->in_mmap && u->in_mmap != MAP_FAILED) {
|
||||
munmap(u->in_mmap, u->in_mmap_length);
|
||||
u->in_mmap = NULL;
|
||||
}
|
||||
|
||||
if (u->out_mmap && u->out_mmap != MAP_FAILED) {
|
||||
munmap(u->out_mmap, u->out_mmap_length);
|
||||
u->out_mmap = NULL;
|
||||
}
|
||||
|
||||
if (u->io_event) {
|
||||
u->core->mainloop->io_free(u->io_event);
|
||||
u->io_event = NULL;
|
||||
}
|
||||
|
||||
if (u->fd >= 0) {
|
||||
close(u->fd);
|
||||
u->fd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
u->core->mempool,
|
||||
(uint8_t*) u->out_mmap+u->out_fragment_size*u->out_current,
|
||||
u->out_fragment_size,
|
||||
1);
|
||||
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("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno));
|
||||
|
||||
clear_up(u);
|
||||
pa_module_unload_request(u->module);
|
||||
return;
|
||||
}
|
||||
|
||||
info.blocks += u->out_blocks_saved;
|
||||
u->out_blocks_saved = 0;
|
||||
|
||||
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(u->core->mempool, (uint8_t*) u->in_mmap+u->in_fragment_size*u->in_current, u->in_fragment_size, 1);
|
||||
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("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno));
|
||||
|
||||
clear_up(u);
|
||||
pa_module_unload_request(u->module);
|
||||
return;
|
||||
}
|
||||
|
||||
info.blocks += u->in_blocks_saved;
|
||||
u->in_blocks_saved = 0;
|
||||
|
||||
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_ERROR) {
|
||||
clear_up(u);
|
||||
pa_module_unload_request(u->module);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
struct count_info info;
|
||||
size_t bpos, n, total;
|
||||
assert(s && u);
|
||||
|
||||
if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) {
|
||||
pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u->out_blocks_saved += info.blocks;
|
||||
|
||||
total = u->out_fragments * u->out_fragment_size;
|
||||
bpos = ((u->out_current + u->out_blocks_saved) * u->out_fragment_size) % total;
|
||||
|
||||
if (bpos <= (size_t) info.ptr)
|
||||
n = total - (info.ptr - bpos);
|
||||
else
|
||||
n = bpos - info.ptr;
|
||||
|
||||
/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->out_fragment_size, u->out_fragments); */
|
||||
|
||||
return pa_bytes_to_usec(n, &s->sample_spec);
|
||||
}
|
||||
|
||||
static pa_usec_t source_get_latency_cb(pa_source *s) {
|
||||
struct userdata *u = s->userdata;
|
||||
struct count_info info;
|
||||
size_t bpos, n, total;
|
||||
assert(s && u);
|
||||
|
||||
if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) {
|
||||
pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u->in_blocks_saved += info.blocks;
|
||||
|
||||
total = u->in_fragments * u->in_fragment_size;
|
||||
bpos = ((u->in_current + u->in_blocks_saved) * u->in_fragment_size) % total;
|
||||
|
||||
if (bpos <= (size_t) info.ptr)
|
||||
n = info.ptr - bpos;
|
||||
else
|
||||
n = (u->in_fragments * u->in_fragment_size) - bpos + info.ptr;
|
||||
|
||||
/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->in_fragment_size, u->in_fragments); */
|
||||
|
||||
return pa_bytes_to_usec(n, &s->sample_spec);
|
||||
}
|
||||
|
||||
static int sink_get_hw_volume(pa_sink *s) {
|
||||
struct userdata *u = s->userdata;
|
||||
|
||||
if (pa_oss_get_pcm_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) {
|
||||
pa_log_info("device doesn't support reading mixer settings: %s", pa_cstrerror(errno));
|
||||
s->get_hw_volume = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sink_set_hw_volume(pa_sink *s) {
|
||||
struct userdata *u = s->userdata;
|
||||
|
||||
if (pa_oss_set_pcm_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) {
|
||||
pa_log_info("device doesn't support writing mixer settings: %s", pa_cstrerror(errno));
|
||||
s->set_hw_volume = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int source_get_hw_volume(pa_source *s) {
|
||||
struct userdata *u = s->userdata;
|
||||
|
||||
if (pa_oss_get_input_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) {
|
||||
pa_log_info("device doesn't support reading mixer settings: %s", pa_cstrerror(errno));
|
||||
s->get_hw_volume = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int source_set_hw_volume(pa_source *s) {
|
||||
struct userdata *u = s->userdata;
|
||||
|
||||
if (pa_oss_set_input_volume(u->fd, &s->sample_spec, &s->hw_volume) < 0) {
|
||||
pa_log_info("device doesn't support writing mixer settings: %s", pa_cstrerror(errno));
|
||||
s->set_hw_volume = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
char hwdesc[64], *t;
|
||||
pa_channel_map map;
|
||||
const char *name;
|
||||
char *name_buf = NULL;
|
||||
int namereg_fail;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
|
||||
m->userdata = u = pa_xnew0(struct userdata, 1);
|
||||
u->module = m;
|
||||
u->fd = -1;
|
||||
u->core = c;
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("failed to parse module arguments.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) {
|
||||
pa_log("record= and playback= expect numeric arguments.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!playback && !record) {
|
||||
pa_log("neither playback nor record enabled for device.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0));
|
||||
|
||||
nfrags = DEFAULT_NFRAGS;
|
||||
frag_size = DEFAULT_FRAGSIZE;
|
||||
if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) {
|
||||
pa_log("failed to parse fragments arguments");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
u->sample_spec = c->default_sample_spec;
|
||||
if (pa_modargs_get_sample_spec_and_channel_map(ma, &u->sample_spec, &map, PA_CHANNEL_MAP_OSS) < 0) {
|
||||
pa_log("failed to parse sample specification or channel map");
|
||||
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_TRIGGER)) {
|
||||
pa_log("OSS device not mmap capable.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pa_log_info("device opened in %s mode.", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR"));
|
||||
|
||||
if (pa_oss_get_hw_description(p, hwdesc, sizeof(hwdesc)) >= 0)
|
||||
pa_log_info("hardware name is '%s'.", hwdesc);
|
||||
else
|
||||
hwdesc[0] = 0;
|
||||
|
||||
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("SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pa_log_info("input -- %u fragments of size %u.", 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("mmap failed for input. Changing to O_WRONLY mode.");
|
||||
mode = O_WRONLY;
|
||||
} else {
|
||||
pa_log("mmap(): %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
if ((name = pa_modargs_get_value(ma, "source_name", NULL)))
|
||||
namereg_fail = 1;
|
||||
else {
|
||||
name = name_buf = pa_sprintf_malloc("oss_input.%s", pa_path_get_filename(p));
|
||||
namereg_fail = 0;
|
||||
}
|
||||
|
||||
if (!(u->source = pa_source_new(c, __FILE__, name, namereg_fail, &u->sample_spec, &map)))
|
||||
goto fail;
|
||||
|
||||
u->source->userdata = u;
|
||||
u->source->get_latency = source_get_latency_cb;
|
||||
u->source->get_hw_volume = source_get_hw_volume;
|
||||
u->source->set_hw_volume = source_set_hw_volume;
|
||||
pa_source_set_owner(u->source, m);
|
||||
pa_source_set_description(u->source, t = pa_sprintf_malloc("OSS PCM/mmap() on %s%s%s%s",
|
||||
p,
|
||||
hwdesc[0] ? " (" : "",
|
||||
hwdesc[0] ? hwdesc : "",
|
||||
hwdesc[0] ? ")" : ""));
|
||||
pa_xfree(t);
|
||||
u->source->is_hardware = 1;
|
||||
|
||||
u->in_memblocks = pa_xnew0(pa_memblock*, u->in_fragments);
|
||||
|
||||
enable_bits |= PCM_ENABLE_INPUT;
|
||||
}
|
||||
}
|
||||
|
||||
pa_xfree(name_buf);
|
||||
name_buf = NULL;
|
||||
|
||||
if (mode != O_RDONLY) {
|
||||
if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) {
|
||||
pa_log("SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pa_log_info("output -- %u fragments of size %u.", 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("mmap filed for output. Changing to O_RDONLY mode.");
|
||||
mode = O_RDONLY;
|
||||
} else {
|
||||
pa_log("mmap(): %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
pa_silence_memory(u->out_mmap, u->out_mmap_length, &u->sample_spec);
|
||||
|
||||
if ((name = pa_modargs_get_value(ma, "sink_name", NULL)))
|
||||
namereg_fail = 1;
|
||||
else {
|
||||
name = name_buf = pa_sprintf_malloc("oss_output.%s", pa_path_get_filename(p));
|
||||
namereg_fail = 0;
|
||||
}
|
||||
|
||||
if (!(u->sink = pa_sink_new(c, __FILE__, name, namereg_fail, &u->sample_spec, &map)))
|
||||
goto fail;
|
||||
|
||||
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);
|
||||
pa_sink_set_description(u->sink, t = pa_sprintf_malloc("OSS PCM/mmap() on %s%s%s%s",
|
||||
p,
|
||||
hwdesc[0] ? " (" : "",
|
||||
hwdesc[0] ? hwdesc : "",
|
||||
hwdesc[0] ? ")" : ""));
|
||||
pa_xfree(t);
|
||||
|
||||
u->sink->is_hardware = 1;
|
||||
u->out_memblocks = pa_xmalloc0(sizeof(struct memblock *)*u->out_fragments);
|
||||
|
||||
enable_bits |= PCM_ENABLE_OUTPUT;
|
||||
}
|
||||
}
|
||||
|
||||
pa_xfree(name_buf);
|
||||
name_buf = NULL;
|
||||
|
||||
zero = 0;
|
||||
if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &zero) < 0) {
|
||||
pa_log("SNDCTL_DSP_SETTRIGGER: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &enable_bits) < 0) {
|
||||
pa_log("SNDCTL_DSP_SETTRIGGER: %s", pa_cstrerror(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);
|
||||
|
||||
/* Read mixer settings */
|
||||
if (u->source)
|
||||
source_get_hw_volume(u->source);
|
||||
if (u->sink)
|
||||
sink_get_hw_volume(u->sink);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
pa__done(c, m);
|
||||
|
||||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
pa_xfree(name_buf);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
struct userdata *u;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
||||
clear_up(u);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
pa_xfree(u);
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -28,22 +28,25 @@
|
|||
#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/ioctl.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/iochannel.h>
|
||||
#include <pulsecore/sink.h>
|
||||
#include <pulsecore/module.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/thread.h>
|
||||
#include <pulsecore/thread-mq.h>
|
||||
#include <pulsecore/rtpoll.h>
|
||||
|
||||
#include "module-pipe-sink-symdef.h"
|
||||
|
||||
|
|
@ -58,20 +61,24 @@ PA_MODULE_USAGE(
|
|||
"rate=<sample rate>"
|
||||
"channel_map=<channel map>")
|
||||
|
||||
#define DEFAULT_FIFO_NAME "/tmp/music.output"
|
||||
#define DEFAULT_FILE_NAME "/tmp/music.output"
|
||||
#define DEFAULT_SINK_NAME "fifo_output"
|
||||
|
||||
struct userdata {
|
||||
pa_core *core;
|
||||
pa_module *module;
|
||||
pa_sink *sink;
|
||||
|
||||
pa_thread *thread;
|
||||
pa_thread_mq thread_mq;
|
||||
pa_rtpoll *rtpoll;
|
||||
|
||||
char *filename;
|
||||
|
||||
pa_sink *sink;
|
||||
pa_iochannel *io;
|
||||
pa_defer_event *defer_event;
|
||||
int fd;
|
||||
|
||||
pa_memchunk memchunk;
|
||||
pa_module *module;
|
||||
|
||||
pa_rtpoll_item *rtpoll_item;
|
||||
};
|
||||
|
||||
static const char* const valid_modargs[] = {
|
||||
|
|
@ -84,133 +91,191 @@ static const char* const valid_modargs[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
static void do_write(struct userdata *u) {
|
||||
ssize_t r;
|
||||
assert(u);
|
||||
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||
struct userdata *u = PA_SINK(o)->userdata;
|
||||
|
||||
u->core->mainloop->defer_enable(u->defer_event, 0);
|
||||
switch (code) {
|
||||
|
||||
if (!pa_iochannel_is_writable(u->io))
|
||||
return;
|
||||
case PA_SINK_MESSAGE_GET_LATENCY: {
|
||||
size_t n = 0;
|
||||
int l;
|
||||
|
||||
pa_module_set_used(u->module, pa_sink_used_by(u->sink));
|
||||
#ifdef TIOCINQ
|
||||
if (ioctl(u->fd, TIOCINQ, &l) >= 0 && l > 0)
|
||||
n = (size_t) l;
|
||||
#endif
|
||||
|
||||
if (!u->memchunk.length)
|
||||
if (pa_sink_render(u->sink, PIPE_BUF, &u->memchunk) < 0)
|
||||
return;
|
||||
n += u->memchunk.length;
|
||||
|
||||
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("write(): %s", pa_cstrerror(errno));
|
||||
return;
|
||||
*((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u->memchunk.index += r;
|
||||
u->memchunk.length -= r;
|
||||
return pa_sink_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
if (u->memchunk.length <= 0) {
|
||||
pa_memblock_unref(u->memchunk.memblock);
|
||||
u->memchunk.memblock = NULL;
|
||||
static void thread_func(void *userdata) {
|
||||
struct userdata *u = userdata;
|
||||
int write_type = 0;
|
||||
|
||||
pa_assert(u);
|
||||
|
||||
pa_log_debug("Thread starting up");
|
||||
|
||||
pa_thread_mq_install(&u->thread_mq);
|
||||
pa_rtpoll_install(u->rtpoll);
|
||||
|
||||
for (;;) {
|
||||
struct pollfd *pollfd;
|
||||
int ret;
|
||||
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
|
||||
|
||||
/* Render some data and write it to the fifo */
|
||||
if (u->sink->thread_info.state == PA_SINK_RUNNING && pollfd->revents) {
|
||||
ssize_t l;
|
||||
void *p;
|
||||
|
||||
if (u->memchunk.length <= 0)
|
||||
pa_sink_render(u->sink, PIPE_BUF, &u->memchunk);
|
||||
|
||||
pa_assert(u->memchunk.length > 0);
|
||||
|
||||
p = pa_memblock_acquire(u->memchunk.memblock);
|
||||
l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type);
|
||||
pa_memblock_release(u->memchunk.memblock);
|
||||
|
||||
pa_assert(l != 0);
|
||||
|
||||
if (l < 0) {
|
||||
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
else if (errno != EAGAIN) {
|
||||
pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
u->memchunk.index += l;
|
||||
u->memchunk.length -= l;
|
||||
|
||||
if (u->memchunk.length <= 0) {
|
||||
pa_memblock_unref(u->memchunk.memblock);
|
||||
pa_memchunk_reset(&u->memchunk);
|
||||
}
|
||||
|
||||
pollfd->revents = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hmm, nothing to do. Let's sleep */
|
||||
pollfd->events = u->sink->thread_info.state == PA_SINK_RUNNING ? POLLOUT : 0;
|
||||
|
||||
if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0)
|
||||
goto fail;
|
||||
|
||||
if (ret == 0)
|
||||
goto finish;
|
||||
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
|
||||
|
||||
if (pollfd->revents & ~POLLOUT) {
|
||||
pa_log("FIFO shutdown.");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
/* If this was no regular exit from the loop we have to continue
|
||||
* processing messages until we received PA_MESSAGE_SHUTDOWN */
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
|
||||
pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
|
||||
|
||||
finish:
|
||||
pa_log_debug("Thread shutting down");
|
||||
}
|
||||
|
||||
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;
|
||||
int pa__init(pa_module*m) {
|
||||
struct userdata *u;
|
||||
struct stat st;
|
||||
const char *p;
|
||||
int fd = -1;
|
||||
pa_sample_spec ss;
|
||||
pa_channel_map map;
|
||||
pa_modargs *ma = NULL;
|
||||
pa_modargs *ma;
|
||||
char *t;
|
||||
struct pollfd *pollfd;
|
||||
|
||||
assert(c && m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("failed to parse module arguments");
|
||||
pa_log("Failed to parse module arguments.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ss = c->default_sample_spec;
|
||||
ss = m->core->default_sample_spec;
|
||||
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
|
||||
pa_log("invalid sample format specification");
|
||||
pa_log("Invalid sample format specification or channel map");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mkfifo(p = pa_modargs_get_value(ma, "file", DEFAULT_FIFO_NAME), 0777);
|
||||
u = pa_xnew0(struct userdata, 1);
|
||||
u->core = m->core;
|
||||
u->module = m;
|
||||
m->userdata = u;
|
||||
pa_memchunk_reset(&u->memchunk);
|
||||
pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
|
||||
u->rtpoll = pa_rtpoll_new();
|
||||
pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
|
||||
|
||||
if ((fd = open(p, O_RDWR)) < 0) {
|
||||
pa_log("open('%s'): %s", p, pa_cstrerror(errno));
|
||||
u->filename = pa_xstrdup(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME));
|
||||
|
||||
mkfifo(u->filename, 0666);
|
||||
if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) {
|
||||
pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pa_fd_set_cloexec(fd, 1);
|
||||
pa_make_fd_cloexec(u->fd);
|
||||
pa_make_fd_nonblock(u->fd);
|
||||
|
||||
if (fstat(fd, &st) < 0) {
|
||||
pa_log("fstat('%s'): %s", p, pa_cstrerror(errno));
|
||||
if (fstat(u->fd, &st) < 0) {
|
||||
pa_log("fstat('%s'): %s", u->filename, pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!S_ISFIFO(st.st_mode)) {
|
||||
pa_log("'%s' is not a FIFO.", p);
|
||||
pa_log("'%s' is not a FIFO.", u->filename);
|
||||
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, &map))) {
|
||||
pa_log("failed to create sink.");
|
||||
if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) {
|
||||
pa_log("Failed to create sink.");
|
||||
goto fail;
|
||||
}
|
||||
u->sink->notify = notify_cb;
|
||||
u->sink->get_latency = get_latency_cb;
|
||||
|
||||
u->sink->parent.process_msg = sink_process_msg;
|
||||
u->sink->userdata = u;
|
||||
pa_sink_set_owner(u->sink, m);
|
||||
pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Unix FIFO sink '%s'", p));
|
||||
u->sink->flags = PA_SINK_LATENCY;
|
||||
|
||||
pa_sink_set_module(u->sink, m);
|
||||
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
|
||||
pa_sink_set_rtpoll(u->sink, u->rtpoll);
|
||||
pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Unix FIFO sink '%s'", u->filename));
|
||||
pa_xfree(t);
|
||||
|
||||
u->io = pa_iochannel_new(c->mainloop, -1, fd);
|
||||
assert(u->io);
|
||||
pa_iochannel_set_callback(u->io, io_callback, u);
|
||||
u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
|
||||
pollfd->fd = u->fd;
|
||||
pollfd->events = pollfd->revents = 0;
|
||||
|
||||
u->memchunk.memblock = NULL;
|
||||
u->memchunk.length = 0;
|
||||
if (!(u->thread = pa_thread_new(thread_func, u))) {
|
||||
pa_log("Failed to create thread.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
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_sink_put(u->sink);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
|
||||
|
|
@ -220,32 +285,48 @@ fail:
|
|||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
|
||||
pa__done(c, m);
|
||||
pa__done(m);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
assert(c && m);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
||||
if (u->sink)
|
||||
pa_sink_unlink(u->sink);
|
||||
|
||||
if (u->thread) {
|
||||
pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
|
||||
pa_thread_free(u->thread);
|
||||
}
|
||||
|
||||
pa_thread_mq_done(&u->thread_mq);
|
||||
|
||||
if (u->sink)
|
||||
pa_sink_unref(u->sink);
|
||||
|
||||
if (u->memchunk.memblock)
|
||||
pa_memblock_unref(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);
|
||||
if (u->rtpoll_item)
|
||||
pa_rtpoll_item_free(u->rtpoll_item);
|
||||
|
||||
assert(u->filename);
|
||||
unlink(u->filename);
|
||||
pa_xfree(u->filename);
|
||||
if (u->rtpoll)
|
||||
pa_rtpoll_free(u->rtpoll);
|
||||
|
||||
if (u->filename) {
|
||||
unlink(u->filename);
|
||||
pa_xfree(u->filename);
|
||||
}
|
||||
|
||||
if (u->fd >= 0)
|
||||
pa_assert_se(pa_close(u->fd) == 0);
|
||||
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,22 +28,24 @@
|
|||
#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/poll.h>
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/iochannel.h>
|
||||
#include <pulsecore/source.h>
|
||||
#include <pulsecore/module.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/thread.h>
|
||||
#include <pulsecore/thread-mq.h>
|
||||
#include <pulsecore/rtpoll.h>
|
||||
|
||||
#include "module-pipe-source-symdef.h"
|
||||
|
||||
|
|
@ -58,18 +60,24 @@ PA_MODULE_USAGE(
|
|||
"rate=<sample rate> "
|
||||
"channel_map=<channel map>")
|
||||
|
||||
#define DEFAULT_FIFO_NAME "/tmp/music.input"
|
||||
#define DEFAULT_FILE_NAME "/tmp/music.input"
|
||||
#define DEFAULT_SOURCE_NAME "fifo_input"
|
||||
|
||||
struct userdata {
|
||||
pa_core *core;
|
||||
pa_module *module;
|
||||
pa_source *source;
|
||||
|
||||
pa_thread *thread;
|
||||
pa_thread_mq thread_mq;
|
||||
pa_rtpoll *rtpoll;
|
||||
|
||||
char *filename;
|
||||
int fd;
|
||||
|
||||
pa_source *source;
|
||||
pa_iochannel *io;
|
||||
pa_module *module;
|
||||
pa_memchunk chunk;
|
||||
pa_memchunk memchunk;
|
||||
|
||||
pa_rtpoll_item *rtpoll_item;
|
||||
};
|
||||
|
||||
static const char* const valid_modargs[] = {
|
||||
|
|
@ -82,109 +90,168 @@ static const char* const valid_modargs[] = {
|
|||
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(u->core->mempool, PIPE_BUF);
|
||||
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("read(): %s", pa_cstrerror(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) {
|
||||
static void thread_func(void *userdata) {
|
||||
struct userdata *u = userdata;
|
||||
assert(u);
|
||||
do_read(u);
|
||||
int read_type = 0;
|
||||
|
||||
pa_assert(u);
|
||||
|
||||
pa_log_debug("Thread starting up");
|
||||
|
||||
pa_thread_mq_install(&u->thread_mq);
|
||||
pa_rtpoll_install(u->rtpoll);
|
||||
|
||||
for (;;) {
|
||||
int ret;
|
||||
struct pollfd *pollfd;
|
||||
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
|
||||
|
||||
/* Try to read some data and pass it on to the source driver */
|
||||
if (u->source->thread_info.state == PA_SOURCE_RUNNING && pollfd->revents) {
|
||||
ssize_t l;
|
||||
void *p;
|
||||
|
||||
if (!u->memchunk.memblock) {
|
||||
u->memchunk.memblock = pa_memblock_new(u->core->mempool, PIPE_BUF);
|
||||
u->memchunk.index = u->memchunk.length = 0;
|
||||
}
|
||||
|
||||
pa_assert(pa_memblock_get_length(u->memchunk.memblock) > u->memchunk.index);
|
||||
|
||||
p = pa_memblock_acquire(u->memchunk.memblock);
|
||||
l = pa_read(u->fd, (uint8_t*) p + u->memchunk.index, pa_memblock_get_length(u->memchunk.memblock) - u->memchunk.index, &read_type);
|
||||
pa_memblock_release(u->memchunk.memblock);
|
||||
|
||||
pa_assert(l != 0); /* EOF cannot happen, since we opened the fifo for both reading and writing */
|
||||
|
||||
if (l < 0) {
|
||||
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
else if (errno != EAGAIN) {
|
||||
pa_log("Faile to read data from FIFO: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
u->memchunk.length = l;
|
||||
pa_source_post(u->source, &u->memchunk);
|
||||
u->memchunk.index += l;
|
||||
|
||||
if (u->memchunk.index >= pa_memblock_get_length(u->memchunk.memblock)) {
|
||||
pa_memblock_unref(u->memchunk.memblock);
|
||||
pa_memchunk_reset(&u->memchunk);
|
||||
}
|
||||
|
||||
pollfd->revents = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hmm, nothing to do. Let's sleep */
|
||||
pollfd->events = u->source->thread_info.state == PA_SOURCE_RUNNING ? POLLIN : 0;
|
||||
|
||||
if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0)
|
||||
goto fail;
|
||||
|
||||
if (ret == 0)
|
||||
goto finish;
|
||||
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
|
||||
if (pollfd->revents & ~POLLIN) {
|
||||
pa_log("FIFO shutdown.");
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
/* If this was no regular exit from the loop we have to continue
|
||||
* processing messages until we received PA_MESSAGE_SHUTDOWN */
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
|
||||
pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
|
||||
|
||||
finish:
|
||||
pa_log_debug("Thread shutting down");
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
struct userdata *u = NULL;
|
||||
int pa__init(pa_module*m) {
|
||||
struct userdata *u;
|
||||
struct stat st;
|
||||
const char *p;
|
||||
int fd = -1;
|
||||
pa_sample_spec ss;
|
||||
pa_channel_map map;
|
||||
pa_modargs *ma = NULL;
|
||||
pa_modargs *ma;
|
||||
char *t;
|
||||
struct pollfd *pollfd;
|
||||
|
||||
assert(c && m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("failed to parse module arguments");
|
||||
pa_log("failed to parse module arguments.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ss = c->default_sample_spec;
|
||||
ss = m->core->default_sample_spec;
|
||||
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
|
||||
pa_log("invalid sample format specification or channel map");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mkfifo(p = pa_modargs_get_value(ma, "file", DEFAULT_FIFO_NAME), 0777);
|
||||
u = pa_xnew0(struct userdata, 1);
|
||||
u->core = m->core;
|
||||
u->module = m;
|
||||
m->userdata = u;
|
||||
pa_memchunk_reset(&u->memchunk);
|
||||
pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
|
||||
u->rtpoll = pa_rtpoll_new();
|
||||
pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
|
||||
|
||||
if ((fd = open(p, O_RDWR)) < 0) {
|
||||
pa_log("open('%s'): %s", p, pa_cstrerror(errno));
|
||||
u->filename = pa_xstrdup(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME));
|
||||
|
||||
mkfifo(u->filename, 0666);
|
||||
if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) {
|
||||
pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
pa_fd_set_cloexec(fd, 1);
|
||||
pa_make_fd_cloexec(u->fd);
|
||||
pa_make_fd_nonblock(u->fd);
|
||||
|
||||
if (fstat(fd, &st) < 0) {
|
||||
pa_log("fstat('%s'): %s", p, pa_cstrerror(errno));
|
||||
if (fstat(u->fd, &st) < 0) {
|
||||
pa_log("fstat('%s'): %s",u->filename, pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!S_ISFIFO(st.st_mode)) {
|
||||
pa_log("'%s' is not a FIFO.", p);
|
||||
pa_log("'%s' is not a FIFO.", u->filename);
|
||||
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, &map))) {
|
||||
pa_log("failed to create source.");
|
||||
if (!(u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) {
|
||||
pa_log("Failed to create source.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
u->source->userdata = u;
|
||||
pa_source_set_owner(u->source, m);
|
||||
pa_source_set_description(u->source, t = pa_sprintf_malloc("Unix FIFO source '%s'", p));
|
||||
u->source->flags = 0;
|
||||
|
||||
pa_source_set_module(u->source, m);
|
||||
pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
|
||||
pa_source_set_rtpoll(u->source, u->rtpoll);
|
||||
pa_source_set_description(u->source, t = pa_sprintf_malloc("Unix FIFO source '%s'", u->filename));
|
||||
pa_xfree(t);
|
||||
|
||||
u->io = pa_iochannel_new(c->mainloop, fd, -1);
|
||||
assert(u->io);
|
||||
pa_iochannel_set_callback(u->io, io_callback, u);
|
||||
u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
|
||||
pollfd->fd = u->fd;
|
||||
pollfd->events = pollfd->revents = 0;
|
||||
|
||||
u->chunk.memblock = NULL;
|
||||
u->chunk.index = u->chunk.length = 0;
|
||||
if (!(u->thread = pa_thread_new(thread_func, u))) {
|
||||
pa_log("Failed to create thread.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
u->module = m;
|
||||
m->userdata = u;
|
||||
pa_source_put(u->source);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
|
||||
|
|
@ -194,31 +261,48 @@ fail:
|
|||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
|
||||
pa__done(c, m);
|
||||
pa__done(m);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
assert(c && m);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
||||
if (u->chunk.memblock)
|
||||
pa_memblock_unref(u->chunk.memblock);
|
||||
if (u->source)
|
||||
pa_source_unlink(u->source);
|
||||
|
||||
pa_source_disconnect(u->source);
|
||||
pa_source_unref(u->source);
|
||||
pa_iochannel_free(u->io);
|
||||
if (u->thread) {
|
||||
pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
|
||||
pa_thread_free(u->thread);
|
||||
}
|
||||
|
||||
assert(u->filename);
|
||||
unlink(u->filename);
|
||||
pa_xfree(u->filename);
|
||||
pa_thread_mq_done(&u->thread_mq);
|
||||
|
||||
if (u->source)
|
||||
pa_source_unref(u->source);
|
||||
|
||||
if (u->memchunk.memblock)
|
||||
pa_memblock_unref(u->memchunk.memblock);
|
||||
|
||||
if (u->rtpoll_item)
|
||||
pa_rtpoll_item_free(u->rtpoll_item);
|
||||
|
||||
if (u->rtpoll)
|
||||
pa_rtpoll_free(u->rtpoll);
|
||||
|
||||
if (u->filename) {
|
||||
unlink(u->filename);
|
||||
pa_xfree(u->filename);
|
||||
}
|
||||
|
||||
if (u->fd >= 0)
|
||||
pa_assert_se(pa_close(u->fd) == 0);
|
||||
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@
|
|||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
|
@ -43,10 +42,9 @@
|
|||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
#include "../pulsecore/winsock.h"
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
|
||||
#include <pulsecore/winsock.h>
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/module.h>
|
||||
#include <pulsecore/socket-server.h>
|
||||
|
|
@ -154,7 +152,6 @@
|
|||
#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_COMMON "sink", "source", "auth-anonymous", "cookie",
|
||||
#ifdef USE_TCP_SOCKETS
|
||||
#include "module-esound-protocol-tcp-symdef.h"
|
||||
|
|
@ -205,10 +202,9 @@ struct userdata {
|
|||
#endif
|
||||
};
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
pa_modargs *ma = NULL;
|
||||
int ret = -1;
|
||||
|
||||
struct userdata *u = NULL;
|
||||
|
||||
#if defined(USE_TCP_SOCKETS)
|
||||
|
|
@ -219,9 +215,13 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
pa_socket_server *s;
|
||||
int r;
|
||||
char tmp[PATH_MAX];
|
||||
|
||||
#if defined(USE_PROTOCOL_ESOUND)
|
||||
char tmp2[PATH_MAX];
|
||||
#endif
|
||||
#endif
|
||||
|
||||
assert(c && m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("Failed to parse module arguments");
|
||||
|
|
@ -239,22 +239,22 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
listen_on = pa_modargs_get_value(ma, "listen", NULL);
|
||||
|
||||
if (listen_on) {
|
||||
s_ipv6 = pa_socket_server_new_ipv6_string(c->mainloop, listen_on, port, TCPWRAP_SERVICE);
|
||||
s_ipv4 = pa_socket_server_new_ipv4_string(c->mainloop, listen_on, port, TCPWRAP_SERVICE);
|
||||
s_ipv6 = pa_socket_server_new_ipv6_string(m->core->mainloop, listen_on, port, TCPWRAP_SERVICE);
|
||||
s_ipv4 = pa_socket_server_new_ipv4_string(m->core->mainloop, listen_on, port, TCPWRAP_SERVICE);
|
||||
} else {
|
||||
s_ipv6 = pa_socket_server_new_ipv6_any(c->mainloop, port, TCPWRAP_SERVICE);
|
||||
s_ipv4 = pa_socket_server_new_ipv4_any(c->mainloop, port, TCPWRAP_SERVICE);
|
||||
s_ipv6 = pa_socket_server_new_ipv6_any(m->core->mainloop, port, TCPWRAP_SERVICE);
|
||||
s_ipv4 = pa_socket_server_new_ipv4_any(m->core->mainloop, port, TCPWRAP_SERVICE);
|
||||
}
|
||||
|
||||
if (!s_ipv4 && !s_ipv6)
|
||||
goto fail;
|
||||
|
||||
if (s_ipv4)
|
||||
if (!(u->protocol_ipv4 = protocol_new(c, s_ipv4, m, ma)))
|
||||
if (!(u->protocol_ipv4 = protocol_new(m->core, s_ipv4, m, ma)))
|
||||
pa_socket_server_unref(s_ipv4);
|
||||
|
||||
if (s_ipv6)
|
||||
if (!(u->protocol_ipv6 = protocol_new(c, s_ipv6, m, ma)))
|
||||
if (!(u->protocol_ipv6 = protocol_new(m->core, s_ipv6, m, ma)))
|
||||
pa_socket_server_unref(s_ipv6);
|
||||
|
||||
if (!u->protocol_ipv4 && !u->protocol_ipv6)
|
||||
|
|
@ -262,18 +262,23 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
|
||||
#else
|
||||
|
||||
pa_runtime_path(pa_modargs_get_value(ma, "socket", UNIX_SOCKET), tmp, sizeof(tmp));
|
||||
u->socket_path = pa_xstrdup(tmp);
|
||||
|
||||
#if defined(USE_PROTOCOL_ESOUND)
|
||||
|
||||
snprintf(tmp2, sizeof(tmp2), "/tmp/.esd-%lu/socket", (unsigned long) getuid());
|
||||
pa_runtime_path(pa_modargs_get_value(ma, "socket", tmp2), tmp, sizeof(tmp));
|
||||
u->socket_path = pa_xstrdup(tmp);
|
||||
|
||||
/* This socket doesn't reside in our own runtime dir but in
|
||||
* /tmp/.esd/, hence we have to create the dir first */
|
||||
|
||||
if (pa_make_secure_parent_dir(u->socket_path, c->is_system_instance ? 0755 : 0700, (uid_t)-1, (gid_t)-1) < 0) {
|
||||
if (pa_make_secure_parent_dir(u->socket_path, m->core->is_system_instance ? 0755 : 0700, (uid_t)-1, (gid_t)-1) < 0) {
|
||||
pa_log("Failed to create socket directory '%s': %s\n", u->socket_path, pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
#else
|
||||
pa_runtime_path(pa_modargs_get_value(ma, "socket", UNIX_SOCKET), tmp, sizeof(tmp));
|
||||
u->socket_path = pa_xstrdup(tmp);
|
||||
#endif
|
||||
|
||||
if ((r = pa_unix_socket_remove_stale(tmp)) < 0) {
|
||||
|
|
@ -284,10 +289,10 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
if (r)
|
||||
pa_log("Removed stale UNIX socket '%s'.", tmp);
|
||||
|
||||
if (!(s = pa_socket_server_new_unix(c->mainloop, tmp)))
|
||||
if (!(s = pa_socket_server_new_unix(m->core->mainloop, tmp)))
|
||||
goto fail;
|
||||
|
||||
if (!(u->protocol_unix = protocol_new(c, s, m, ma)))
|
||||
if (!(u->protocol_unix = protocol_new(m->core, s, m, ma)))
|
||||
goto fail;
|
||||
|
||||
#endif
|
||||
|
|
@ -333,11 +338,10 @@ fail:
|
|||
goto finish;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(m);
|
||||
|
||||
u = m->userdata;
|
||||
|
||||
|
|
@ -358,7 +362,6 @@ void pa__done(pa_core *c, pa_module*m) {
|
|||
}
|
||||
#endif
|
||||
|
||||
|
||||
pa_xfree(u->socket_path);
|
||||
#endif
|
||||
|
||||
|
|
|
|||
334
src/modules/module-remap-sink.c
Normal file
334
src/modules/module-remap-sink.c
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
/* $Id$ */
|
||||
|
||||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2004-2006 Lennart Poettering
|
||||
|
||||
PulseAudio 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.
|
||||
|
||||
PulseAudio 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 PulseAudio; 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 <pulse/xmalloc.h>
|
||||
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/namereg.h>
|
||||
#include <pulsecore/sink.h>
|
||||
#include <pulsecore/module.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/thread.h>
|
||||
#include <pulsecore/thread-mq.h>
|
||||
#include <pulsecore/rtpoll.h>
|
||||
|
||||
#include "module-remap-sink-symdef.h"
|
||||
|
||||
PA_MODULE_AUTHOR("Lennart Poettering")
|
||||
PA_MODULE_DESCRIPTION("Virtual channel remapping sink")
|
||||
PA_MODULE_VERSION(PACKAGE_VERSION)
|
||||
PA_MODULE_USAGE(
|
||||
"sink_name=<name for the sink> "
|
||||
"master=<name of sink to remap> "
|
||||
"master_channel_map=<channel map> "
|
||||
"format=<sample format> "
|
||||
"channels=<number of channels> "
|
||||
"rate=<sample rate> "
|
||||
"channel_map=<channel map>")
|
||||
|
||||
struct userdata {
|
||||
pa_core *core;
|
||||
pa_module *module;
|
||||
|
||||
pa_sink *sink, *master;
|
||||
pa_sink_input *sink_input;
|
||||
|
||||
pa_memchunk memchunk;
|
||||
};
|
||||
|
||||
static const char* const valid_modargs[] = {
|
||||
"sink_name",
|
||||
"master",
|
||||
"master_channel_map",
|
||||
"rate",
|
||||
"format",
|
||||
"channels",
|
||||
"channel_map",
|
||||
NULL
|
||||
};
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||
struct userdata *u = PA_SINK(o)->userdata;
|
||||
|
||||
switch (code) {
|
||||
|
||||
case PA_SINK_MESSAGE_GET_LATENCY: {
|
||||
pa_usec_t usec = 0;
|
||||
|
||||
if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0)
|
||||
usec = 0;
|
||||
|
||||
*((pa_usec_t*) data) = usec + pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return pa_sink_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
/* Called from main context */
|
||||
static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_sink_assert_ref(s);
|
||||
pa_assert_se(u = s->userdata);
|
||||
|
||||
if (PA_SINK_LINKED(state) && u->sink_input && PA_SINK_INPUT_LINKED(pa_sink_input_get_state(u->sink_input)))
|
||||
pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||
struct userdata *u = PA_SINK_INPUT(o)->userdata;
|
||||
|
||||
switch (code) {
|
||||
case PA_SINK_INPUT_MESSAGE_GET_LATENCY:
|
||||
*((pa_usec_t*) data) = pa_bytes_to_usec(u->memchunk.length, &u->sink_input->sample_spec);
|
||||
|
||||
/* Fall through, the default handler will add in the extra
|
||||
* latency added by the resampler */
|
||||
break;
|
||||
}
|
||||
|
||||
return pa_sink_input_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(u = i->userdata);
|
||||
|
||||
if (!u->memchunk.memblock)
|
||||
pa_sink_render(u->sink, length, &u->memchunk);
|
||||
|
||||
pa_assert(u->memchunk.memblock);
|
||||
*chunk = u->memchunk;
|
||||
pa_memblock_ref(chunk->memblock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(u = i->userdata);
|
||||
pa_assert(length > 0);
|
||||
|
||||
if (u->memchunk.memblock) {
|
||||
|
||||
if (length < u->memchunk.length) {
|
||||
u->memchunk.index += length;
|
||||
u->memchunk.length -= length;
|
||||
return;
|
||||
}
|
||||
|
||||
pa_memblock_unref(u->memchunk.memblock);
|
||||
length -= u->memchunk.length;
|
||||
pa_memchunk_reset(&u->memchunk);
|
||||
}
|
||||
|
||||
if (length > 0)
|
||||
pa_sink_skip(u->sink, length);
|
||||
}
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static void sink_input_detach_cb(pa_sink_input *i) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(u = i->userdata);
|
||||
|
||||
pa_sink_detach_within_thread(u->sink);
|
||||
}
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static void sink_input_attach_cb(pa_sink_input *i) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(u = i->userdata);
|
||||
|
||||
pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq);
|
||||
pa_sink_set_rtpoll(u->sink, i->sink->rtpoll);
|
||||
|
||||
pa_sink_attach_within_thread(u->sink);
|
||||
}
|
||||
|
||||
/* Called from main context */
|
||||
static void sink_input_kill_cb(pa_sink_input *i) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(u = i->userdata);
|
||||
|
||||
pa_sink_input_unlink(u->sink_input);
|
||||
pa_sink_input_unref(u->sink_input);
|
||||
u->sink_input = NULL;
|
||||
|
||||
pa_sink_unlink(u->sink);
|
||||
pa_sink_unref(u->sink);
|
||||
u->sink = NULL;
|
||||
|
||||
pa_module_unload_request(u->module);
|
||||
}
|
||||
|
||||
int pa__init(pa_module*m) {
|
||||
struct userdata *u;
|
||||
pa_sample_spec ss;
|
||||
pa_channel_map sink_map, stream_map;
|
||||
pa_modargs *ma;
|
||||
char *t;
|
||||
pa_sink *master;
|
||||
pa_sink_input_new_data data;
|
||||
char *default_sink_name = NULL;
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("Failed to parse module arguments.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK, 1))) {
|
||||
pa_log("Master sink not found");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ss = master->sample_spec;
|
||||
sink_map = master->channel_map;
|
||||
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &sink_map, PA_CHANNEL_MAP_DEFAULT) < 0) {
|
||||
pa_log("Invalid sample format specification or channel map");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
stream_map = sink_map;
|
||||
if (pa_modargs_get_channel_map(ma, "master_channel_map", &stream_map) < 0) {
|
||||
pa_log("Invalid master hannel map");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (stream_map.channels != ss.channels) {
|
||||
pa_log("Number of channels doesn't match");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
u = pa_xnew0(struct userdata, 1);
|
||||
u->core = m->core;
|
||||
u->module = m;
|
||||
m->userdata = u;
|
||||
u->master = master;
|
||||
pa_memchunk_reset(&u->memchunk);
|
||||
|
||||
default_sink_name = pa_sprintf_malloc("%s.remapped", master->name);
|
||||
|
||||
/* Create sink */
|
||||
if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", default_sink_name), 0, &ss, &sink_map))) {
|
||||
pa_log("Failed to create sink.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
u->sink->parent.process_msg = sink_process_msg;
|
||||
u->sink->set_state = sink_set_state;
|
||||
u->sink->userdata = u;
|
||||
u->sink->flags = PA_SINK_LATENCY;
|
||||
|
||||
pa_sink_set_module(u->sink, m);
|
||||
pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Remapped %s", master->description));
|
||||
pa_xfree(t);
|
||||
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
|
||||
pa_sink_set_rtpoll(u->sink, master->rtpoll);
|
||||
|
||||
/* Create sink input */
|
||||
pa_sink_input_new_data_init(&data);
|
||||
data.sink = u->master;
|
||||
data.driver = __FILE__;
|
||||
data.name = "Remapped Stream";
|
||||
pa_sink_input_new_data_set_sample_spec(&data, &ss);
|
||||
pa_sink_input_new_data_set_channel_map(&data, &stream_map);
|
||||
data.module = m;
|
||||
|
||||
if (!(u->sink_input = pa_sink_input_new(m->core, &data, PA_SINK_INPUT_DONT_MOVE)))
|
||||
goto fail;
|
||||
|
||||
u->sink_input->parent.process_msg = sink_input_process_msg;
|
||||
u->sink_input->peek = sink_input_peek_cb;
|
||||
u->sink_input->drop = sink_input_drop_cb;
|
||||
u->sink_input->kill = sink_input_kill_cb;
|
||||
u->sink_input->attach = sink_input_attach_cb;
|
||||
u->sink_input->detach = sink_input_detach_cb;
|
||||
u->sink_input->userdata = u;
|
||||
|
||||
pa_sink_put(u->sink);
|
||||
pa_sink_input_put(u->sink_input);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
pa_xfree(default_sink_name);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
pa__done(m);
|
||||
|
||||
pa_xfree(default_sink_name);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
||||
if (u->sink_input) {
|
||||
pa_sink_input_unlink(u->sink_input);
|
||||
pa_sink_input_unref(u->sink_input);
|
||||
}
|
||||
|
||||
if (u->sink) {
|
||||
pa_sink_unlink(u->sink);
|
||||
pa_sink_unref(u->sink);
|
||||
}
|
||||
|
||||
if (u->memchunk.memblock)
|
||||
pa_memblock_unref(u->memchunk.memblock);
|
||||
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
|
@ -52,20 +52,26 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user
|
|||
pa_sink_input *i;
|
||||
pa_sink *target;
|
||||
|
||||
assert(c);
|
||||
assert(sink);
|
||||
pa_assert(c);
|
||||
pa_assert(sink);
|
||||
|
||||
if (!pa_idxset_size(sink->inputs)) {
|
||||
pa_log_debug("No sink inputs to move away.");
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SINK, 0))) {
|
||||
pa_log_info("No evacuation sink found.");
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SINK, 0)) || target == sink) {
|
||||
uint32_t idx;
|
||||
|
||||
assert(target != sink);
|
||||
for (target = pa_idxset_first(c->sinks, &idx); target; target = pa_idxset_next(c->sinks, &idx))
|
||||
if (target != sink)
|
||||
break;
|
||||
|
||||
if (!target) {
|
||||
pa_log_info("No evacuation sink found.");
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
}
|
||||
|
||||
while ((i = pa_idxset_first(sink->inputs, NULL))) {
|
||||
if (pa_sink_input_move_to(i, target, 1) < 0) {
|
||||
|
|
@ -84,20 +90,28 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void
|
|||
pa_source_output *o;
|
||||
pa_source *target;
|
||||
|
||||
assert(c);
|
||||
assert(source);
|
||||
pa_assert(c);
|
||||
pa_assert(source);
|
||||
|
||||
if (!pa_idxset_size(source->outputs)) {
|
||||
pa_log_debug("No source outputs to move away.");
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SOURCE, 0))) {
|
||||
pa_log_info("No evacuation source found.");
|
||||
return PA_HOOK_OK;
|
||||
if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SOURCE, 0)) || target == source) {
|
||||
uint32_t idx;
|
||||
|
||||
for (target = pa_idxset_first(c->sources, &idx); target; target = pa_idxset_next(c->sources, &idx))
|
||||
if (target != source && !target->monitor_of == !source->monitor_of)
|
||||
break;
|
||||
|
||||
if (!target) {
|
||||
pa_log_info("No evacuation source found.");
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
}
|
||||
|
||||
assert(target != source);
|
||||
pa_assert(target != source);
|
||||
|
||||
while ((o = pa_idxset_first(source->outputs, NULL))) {
|
||||
if (pa_source_output_move_to(o, target) < 0) {
|
||||
|
|
@ -112,12 +126,11 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void
|
|||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
pa_modargs *ma = NULL;
|
||||
struct userdata *u;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("Failed to parse module arguments");
|
||||
|
|
@ -125,18 +138,17 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
}
|
||||
|
||||
m->userdata = u = pa_xnew(struct userdata, 1);
|
||||
u->sink_slot = pa_hook_connect(&c->hook_sink_disconnect, (pa_hook_cb_t) sink_hook_callback, NULL);
|
||||
u->source_slot = pa_hook_connect(&c->hook_source_disconnect, (pa_hook_cb_t) source_hook_callback, NULL);
|
||||
u->sink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) sink_hook_callback, NULL);
|
||||
u->source_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], (pa_hook_cb_t) source_hook_callback, NULL);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!m->userdata)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@
|
|||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
|
|
@ -36,6 +35,7 @@
|
|||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/namereg.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
|
||||
#include "module-sine-symdef.h"
|
||||
|
||||
|
|
@ -58,36 +58,46 @@ static const char* const valid_modargs[] = {
|
|||
NULL,
|
||||
};
|
||||
|
||||
static int sink_input_peek(pa_sink_input *i, pa_memchunk *chunk) {
|
||||
static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
|
||||
struct userdata *u;
|
||||
assert(i && chunk && i->userdata);
|
||||
|
||||
pa_assert(i);
|
||||
u = i->userdata;
|
||||
pa_assert(u);
|
||||
pa_assert(chunk);
|
||||
|
||||
chunk->memblock = pa_memblock_ref(u->memblock);
|
||||
chunk->index = u->peek_index;
|
||||
chunk->length = u->memblock->length - u->peek_index;
|
||||
chunk->length = pa_memblock_get_length(u->memblock) - u->peek_index;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sink_input_drop(pa_sink_input *i, const pa_memchunk *chunk, size_t length) {
|
||||
static void sink_input_drop_cb(pa_sink_input *i, size_t length) {
|
||||
struct userdata *u;
|
||||
assert(i && chunk && length && i->userdata);
|
||||
u = i->userdata;
|
||||
size_t l;
|
||||
|
||||
assert(chunk->memblock == u->memblock && length <= u->memblock->length-u->peek_index);
|
||||
pa_assert(i);
|
||||
u = i->userdata;
|
||||
pa_assert(u);
|
||||
pa_assert(length > 0);
|
||||
|
||||
u->peek_index += length;
|
||||
|
||||
if (u->peek_index >= u->memblock->length)
|
||||
u->peek_index = 0;
|
||||
l = pa_memblock_get_length(u->memblock);
|
||||
|
||||
while (u->peek_index >= l)
|
||||
u->peek_index -= l;
|
||||
}
|
||||
|
||||
static void sink_input_kill(pa_sink_input *i) {
|
||||
static void sink_input_kill_cb(pa_sink_input *i) {
|
||||
struct userdata *u;
|
||||
assert(i && i->userdata);
|
||||
u = i->userdata;
|
||||
|
||||
pa_sink_input_disconnect(u->sink_input);
|
||||
pa_assert(i);
|
||||
u = i->userdata;
|
||||
pa_assert(u);
|
||||
|
||||
pa_sink_input_unlink(u->sink_input);
|
||||
pa_sink_input_unref(u->sink_input);
|
||||
u->sink_input = NULL;
|
||||
|
||||
|
|
@ -103,14 +113,14 @@ static void calc_sine(float *f, size_t l, float freq) {
|
|||
f[i] = (float) sin((double) i/l*M_PI*2*freq)/2;
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(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];
|
||||
void *p;
|
||||
pa_sink_input_new_data data;
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
|
|
@ -118,15 +128,14 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
m->userdata = u = pa_xmalloc(sizeof(struct userdata));
|
||||
u->core = c;
|
||||
m->userdata = u = pa_xnew0(struct userdata, 1);
|
||||
u->core = m->core;
|
||||
u->module = m;
|
||||
u->sink_input = NULL;
|
||||
u->memblock = NULL;
|
||||
u->peek_index = 0;
|
||||
|
||||
sink_name = pa_modargs_get_value(ma, "sink", NULL);
|
||||
|
||||
if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK, 1))) {
|
||||
if (!(sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sink", NULL), PA_NAMEREG_SINK, 1))) {
|
||||
pa_log("No such sink.");
|
||||
goto fail;
|
||||
}
|
||||
|
|
@ -141,10 +150,12 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
u->memblock = pa_memblock_new(c->mempool, pa_bytes_per_second(&ss));
|
||||
calc_sine(u->memblock->data, u->memblock->length, frequency);
|
||||
u->memblock = pa_memblock_new(m->core->mempool, pa_bytes_per_second(&ss));
|
||||
p = pa_memblock_acquire(u->memblock);
|
||||
calc_sine(p, pa_memblock_get_length(u->memblock), frequency);
|
||||
pa_memblock_release(u->memblock);
|
||||
|
||||
snprintf(t, sizeof(t), "Sine Generator at %u Hz", frequency);
|
||||
pa_snprintf(t, sizeof(t), "Sine Generator at %u Hz", frequency);
|
||||
|
||||
pa_sink_input_new_data_init(&data);
|
||||
data.sink = sink;
|
||||
|
|
@ -153,15 +164,15 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
pa_sink_input_new_data_set_sample_spec(&data, &ss);
|
||||
data.module = m;
|
||||
|
||||
if (!(u->sink_input = pa_sink_input_new(c, &data, 0)))
|
||||
if (!(u->sink_input = pa_sink_input_new(m->core, &data, 0)))
|
||||
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->peek = sink_input_peek_cb;
|
||||
u->sink_input->drop = sink_input_drop_cb;
|
||||
u->sink_input->kill = sink_input_kill_cb;
|
||||
u->sink_input->userdata = u;
|
||||
|
||||
u->peek_index = 0;
|
||||
pa_sink_input_put(u->sink_input);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
return 0;
|
||||
|
|
@ -170,24 +181,26 @@ fail:
|
|||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
pa__done(c, m);
|
||||
pa__done(m);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
struct userdata *u = m->userdata;
|
||||
assert(c && m);
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
|
||||
if (!u)
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
||||
if (u->sink_input) {
|
||||
pa_sink_input_disconnect(u->sink_input);
|
||||
pa_sink_input_unlink(u->sink_input);
|
||||
pa_sink_input_unref(u->sink_input);
|
||||
}
|
||||
|
||||
if (u->memblock)
|
||||
pa_memblock_unref(u->memblock);
|
||||
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2006 Lennart Poettering
|
||||
Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
|
||||
Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB
|
||||
|
||||
PulseAudio is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published
|
||||
|
|
@ -57,6 +57,9 @@
|
|||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/thread-mq.h>
|
||||
#include <pulsecore/rtpoll.h>
|
||||
#include <pulsecore/thread.h>
|
||||
|
||||
#include "module-solaris-symdef.h"
|
||||
|
||||
|
|
@ -75,12 +78,14 @@ PA_MODULE_USAGE(
|
|||
"channel_map=<channel map>")
|
||||
|
||||
struct userdata {
|
||||
pa_core *core;
|
||||
pa_sink *sink;
|
||||
pa_source *source;
|
||||
pa_iochannel *io;
|
||||
pa_core *core;
|
||||
pa_time_event *timer;
|
||||
pa_usec_t poll_timeout;
|
||||
|
||||
pa_thread *thread;
|
||||
pa_thread_mq thread_mq;
|
||||
pa_rtpoll *rtpoll;
|
||||
|
||||
pa_signal_event *sig;
|
||||
|
||||
pa_memchunk memchunk;
|
||||
|
|
@ -90,9 +95,9 @@ struct userdata {
|
|||
uint32_t frame_size;
|
||||
uint32_t buffer_size;
|
||||
unsigned int written_bytes, read_bytes;
|
||||
int sink_underflow;
|
||||
|
||||
int fd;
|
||||
pa_rtpoll_item *rtpoll_item;
|
||||
pa_module *module;
|
||||
};
|
||||
|
||||
|
|
@ -114,309 +119,357 @@ static const char* const valid_modargs[] = {
|
|||
#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_sink_used_by(u->sink) : 0) +
|
||||
(u->source ? pa_source_used_by(u->source) : 0));
|
||||
}
|
||||
|
||||
static void do_write(struct userdata *u) {
|
||||
static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||
struct userdata *u = PA_SINK(o)->userdata;
|
||||
int err;
|
||||
audio_info_t info;
|
||||
int err;
|
||||
size_t len;
|
||||
ssize_t r;
|
||||
|
||||
assert(u);
|
||||
switch (code) {
|
||||
case PA_SINK_MESSAGE_GET_LATENCY: {
|
||||
pa_usec_t r = 0;
|
||||
|
||||
/* We cannot check pa_iochannel_is_writable() because of our buffer hack */
|
||||
if (!u->sink)
|
||||
return;
|
||||
if (u->fd >= 0) {
|
||||
|
||||
update_usage(u);
|
||||
err = ioctl(u->fd, AUDIO_GETINFO, &info);
|
||||
pa_assert(err >= 0);
|
||||
|
||||
err = ioctl(u->fd, AUDIO_GETINFO, &info);
|
||||
assert(err >= 0);
|
||||
r += pa_bytes_to_usec(u->written_bytes, &PA_SINK(o)->sample_spec);
|
||||
r -= pa_bytes_to_usec(info.play.samples * u->frame_size, &PA_SINK(o)->sample_spec);
|
||||
|
||||
/*
|
||||
* 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->frame_size);
|
||||
|
||||
/* The sample counter can sometimes go backwards :( */
|
||||
if (len > u->buffer_size)
|
||||
len = 0;
|
||||
|
||||
if (!u->sink_underflow && (len == u->buffer_size))
|
||||
pa_log_debug("Solaris buffer underflow!");
|
||||
|
||||
len -= len % u->frame_size;
|
||||
|
||||
if (len == 0)
|
||||
return;
|
||||
|
||||
if (!u->memchunk.length) {
|
||||
if (pa_sink_render(u->sink, len, &u->memchunk) < 0) {
|
||||
u->sink_underflow = 1;
|
||||
return;
|
||||
if (u->memchunk.memblock)
|
||||
r += pa_bytes_to_usec(u->memchunk.length, &PA_SINK(o)->sample_spec);
|
||||
}
|
||||
|
||||
*((pa_usec_t*) data) = r;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u->sink_underflow = 0;
|
||||
case PA_SINK_MESSAGE_SET_VOLUME:
|
||||
if (u->fd >= 0) {
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
assert(u->memchunk.memblock);
|
||||
assert(u->memchunk.memblock->data);
|
||||
assert(u->memchunk.length);
|
||||
info.play.gain = pa_cvolume_avg((pa_cvolume*)data) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
|
||||
assert(info.play.gain <= AUDIO_MAX_GAIN);
|
||||
|
||||
if (u->memchunk.length < len) {
|
||||
len = u->memchunk.length;
|
||||
len -= len % u->frame_size;
|
||||
assert(len);
|
||||
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
|
||||
if (errno == EINVAL)
|
||||
pa_log("AUDIO_SETINFO: Unsupported volume.");
|
||||
else
|
||||
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PA_SINK_MESSAGE_GET_VOLUME:
|
||||
if (u->fd >= 0) {
|
||||
err = ioctl(u->fd, AUDIO_GETINFO, &info);
|
||||
assert(err >= 0);
|
||||
|
||||
pa_cvolume_set((pa_cvolume*) data, ((pa_cvolume*) data)->channels,
|
||||
info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case PA_SINK_MESSAGE_SET_MUTE:
|
||||
if (u->fd >= 0) {
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
info.output_muted = !!PA_PTR_TO_UINT(data);
|
||||
|
||||
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0)
|
||||
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case PA_SINK_MESSAGE_GET_MUTE:
|
||||
if (u->fd >= 0) {
|
||||
err = ioctl(u->fd, AUDIO_GETINFO, &info);
|
||||
pa_assert(err >= 0);
|
||||
|
||||
*(int*)data = !!info.output_muted;
|
||||
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ((r = pa_iochannel_write(u->io,
|
||||
(uint8_t*) u->memchunk.memblock->data + u->memchunk.index, len)) < 0) {
|
||||
pa_log("write() failed: %s", pa_cstrerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
assert(r % u->frame_size == 0);
|
||||
|
||||
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;
|
||||
return pa_sink_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
static void do_read(struct userdata *u) {
|
||||
pa_memchunk memchunk;
|
||||
static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||
struct userdata *u = PA_SOURCE(o)->userdata;
|
||||
int err;
|
||||
size_t l;
|
||||
ssize_t r;
|
||||
assert(u);
|
||||
audio_info_t info;
|
||||
|
||||
if (!u->source || !pa_iochannel_is_readable(u->io))
|
||||
return;
|
||||
switch (code) {
|
||||
case PA_SOURCE_MESSAGE_GET_LATENCY: {
|
||||
pa_usec_t r = 0;
|
||||
|
||||
update_usage(u);
|
||||
if (u->fd) {
|
||||
err = ioctl(u->fd, AUDIO_GETINFO, &info);
|
||||
pa_assert(err >= 0);
|
||||
|
||||
err = ioctl(u->fd, I_NREAD, &l);
|
||||
assert(err >= 0);
|
||||
r += pa_bytes_to_usec(info.record.samples * u->frame_size, &PA_SOURCE(o)->sample_spec);
|
||||
r -= pa_bytes_to_usec(u->read_bytes, &PA_SOURCE(o)->sample_spec);
|
||||
}
|
||||
|
||||
/* This is to make sure it fits in the memory pool. Also, a page
|
||||
should be the most efficient transfer size. */
|
||||
if (l > u->page_size)
|
||||
l = u->page_size;
|
||||
*((pa_usec_t*) data) = r;
|
||||
|
||||
memchunk.memblock = pa_memblock_new(u->core->mempool, l);
|
||||
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("read() failed: %s", pa_cstrerror(errno));
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
case PA_SOURCE_MESSAGE_SET_VOLUME:
|
||||
if (u->fd >= 0) {
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
info.record.gain = pa_cvolume_avg((pa_cvolume*) data) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
|
||||
assert(info.record.gain <= AUDIO_MAX_GAIN);
|
||||
|
||||
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
|
||||
if (errno == EINVAL)
|
||||
pa_log("AUDIO_SETINFO: Unsupported volume.");
|
||||
else
|
||||
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PA_SOURCE_MESSAGE_GET_VOLUME:
|
||||
if (u->fd >= 0) {
|
||||
err = ioctl(u->fd, AUDIO_GETINFO, &info);
|
||||
pa_assert(err >= 0);
|
||||
|
||||
pa_cvolume_set((pa_cvolume*) data, ((pa_cvolume*) data)->channels,
|
||||
info.record.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
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);
|
||||
|
||||
u->read_bytes += r;
|
||||
return pa_source_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
static void io_callback(pa_iochannel *io, void*userdata) {
|
||||
struct userdata *u = userdata;
|
||||
assert(u);
|
||||
do_write(u);
|
||||
do_read(u);
|
||||
static void clear_underflow(struct userdata *u)
|
||||
{
|
||||
audio_info_t info;
|
||||
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
info.play.error = 0;
|
||||
|
||||
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0)
|
||||
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
|
||||
}
|
||||
|
||||
static void timer_cb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) {
|
||||
static void clear_overflow(struct userdata *u)
|
||||
{
|
||||
audio_info_t info;
|
||||
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
info.record.error = 0;
|
||||
|
||||
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0)
|
||||
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
|
||||
}
|
||||
|
||||
static void thread_func(void *userdata) {
|
||||
struct userdata *u = userdata;
|
||||
struct timeval ntv;
|
||||
unsigned short revents = 0;
|
||||
int ret;
|
||||
|
||||
assert(u);
|
||||
pa_assert(u);
|
||||
|
||||
do_write(u);
|
||||
pa_log_debug("Thread starting up");
|
||||
|
||||
pa_gettimeofday(&ntv);
|
||||
pa_timeval_add(&ntv, u->poll_timeout);
|
||||
if (u->core->high_priority)
|
||||
pa_make_realtime();
|
||||
|
||||
a->time_restart(e, &ntv);
|
||||
pa_thread_mq_install(&u->thread_mq);
|
||||
pa_rtpoll_install(u->rtpoll);
|
||||
|
||||
for (;;) {
|
||||
/* Render some data and write it to the dsp */
|
||||
|
||||
if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state)) {
|
||||
audio_info_t info;
|
||||
int err;
|
||||
size_t len;
|
||||
|
||||
err = ioctl(u->fd, AUDIO_GETINFO, &info);
|
||||
pa_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->frame_size);
|
||||
|
||||
/* The sample counter can sometimes go backwards :( */
|
||||
if (len > u->buffer_size)
|
||||
len = 0;
|
||||
|
||||
if (info.play.error) {
|
||||
pa_log_debug("Solaris buffer underflow!");
|
||||
clear_underflow(u);
|
||||
}
|
||||
|
||||
len -= len % u->frame_size;
|
||||
|
||||
while (len) {
|
||||
void *p;
|
||||
ssize_t r;
|
||||
|
||||
if (!u->memchunk.length)
|
||||
pa_sink_render(u->sink, len, &u->memchunk);
|
||||
|
||||
pa_assert(u->memchunk.length);
|
||||
|
||||
p = pa_memblock_acquire(u->memchunk.memblock);
|
||||
r = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, NULL);
|
||||
pa_memblock_release(u->memchunk.memblock);
|
||||
|
||||
if (r < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
else if (errno != EAGAIN) {
|
||||
pa_log("Failed to read data from DSP: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
pa_assert(r % u->frame_size == 0);
|
||||
|
||||
u->memchunk.index += r;
|
||||
u->memchunk.length -= r;
|
||||
|
||||
if (u->memchunk.length <= 0) {
|
||||
pa_memblock_unref(u->memchunk.memblock);
|
||||
pa_memchunk_reset(&u->memchunk);
|
||||
}
|
||||
|
||||
len -= r;
|
||||
u->written_bytes += r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to read some data and pass it on to the source driver */
|
||||
|
||||
if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state) && ((revents & POLLIN))) {
|
||||
pa_memchunk memchunk;
|
||||
int err;
|
||||
size_t l;
|
||||
void *p;
|
||||
ssize_t r;
|
||||
audio_info_t info;
|
||||
|
||||
err = ioctl(u->fd, AUDIO_GETINFO, &info);
|
||||
pa_assert(err >= 0);
|
||||
|
||||
if (info.record.error) {
|
||||
pa_log_debug("Solaris buffer overflow!");
|
||||
clear_overflow(u);
|
||||
}
|
||||
|
||||
err = ioctl(u->fd, I_NREAD, &l);
|
||||
pa_assert(err >= 0);
|
||||
|
||||
if (l > 0) {
|
||||
/* This is to make sure it fits in the memory pool. Also, a page
|
||||
should be the most efficient transfer size. */
|
||||
if (l > u->page_size)
|
||||
l = u->page_size;
|
||||
|
||||
memchunk.memblock = pa_memblock_new(u->core->mempool, l);
|
||||
pa_assert(memchunk.memblock);
|
||||
|
||||
p = pa_memblock_acquire(memchunk.memblock);
|
||||
r = pa_read(u->fd, p, l, NULL);
|
||||
pa_memblock_release(memchunk.memblock);
|
||||
|
||||
if (r < 0) {
|
||||
pa_memblock_unref(memchunk.memblock);
|
||||
if (errno != EAGAIN) {
|
||||
pa_log("Failed to read data from DSP: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
memchunk.index = 0;
|
||||
memchunk.length = r;
|
||||
|
||||
pa_source_post(u->source, &memchunk);
|
||||
pa_memblock_unref(memchunk.memblock);
|
||||
|
||||
u->read_bytes += r;
|
||||
|
||||
revents &= ~POLLIN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (u->fd >= 0) {
|
||||
struct pollfd *pollfd;
|
||||
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
|
||||
pollfd->events =
|
||||
((u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) ? POLLIN : 0);
|
||||
}
|
||||
|
||||
/* Hmm, nothing to do. Let's sleep */
|
||||
if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0)
|
||||
goto fail;
|
||||
|
||||
if (ret == 0)
|
||||
goto finish;
|
||||
|
||||
if (u->fd >= 0) {
|
||||
struct pollfd *pollfd;
|
||||
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
|
||||
|
||||
if (pollfd->revents & ~(POLLOUT|POLLIN)) {
|
||||
pa_log("DSP shutdown.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
revents = pollfd->revents;
|
||||
} else
|
||||
revents = 0;
|
||||
}
|
||||
|
||||
fail:
|
||||
/* We have to continue processing messages until we receive the
|
||||
* SHUTDOWN message */
|
||||
pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
|
||||
pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
|
||||
|
||||
finish:
|
||||
pa_log_debug("Thread shutting down");
|
||||
}
|
||||
|
||||
static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata) {
|
||||
struct userdata *u = userdata;
|
||||
pa_cvolume old_vol;
|
||||
|
||||
assert(u);
|
||||
|
||||
if (u->sink) {
|
||||
assert(u->sink->get_hw_volume);
|
||||
memcpy(&old_vol, &u->sink->hw_volume, sizeof(pa_cvolume));
|
||||
if (u->sink->get_hw_volume(u->sink) < 0)
|
||||
return;
|
||||
if (memcmp(&old_vol, &u->sink->hw_volume, sizeof(pa_cvolume)) != 0) {
|
||||
pa_subscription_post(u->sink->core,
|
||||
PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE,
|
||||
u->sink->index);
|
||||
}
|
||||
pa_sink_get_volume(u->sink);
|
||||
pa_sink_get_mute(u->sink);
|
||||
}
|
||||
|
||||
if (u->source) {
|
||||
assert(u->source->get_hw_volume);
|
||||
memcpy(&old_vol, &u->source->hw_volume, sizeof(pa_cvolume));
|
||||
if (u->source->get_hw_volume(u->source) < 0)
|
||||
return;
|
||||
if (memcmp(&old_vol, &u->source->hw_volume, sizeof(pa_cvolume)) != 0) {
|
||||
pa_subscription_post(u->source->core,
|
||||
PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE,
|
||||
u->source->index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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->frame_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->frame_size, &s->sample_spec);
|
||||
r -= pa_bytes_to_usec(u->read_bytes, &s->sample_spec);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static int sink_get_hw_volume_cb(pa_sink *s) {
|
||||
struct userdata *u = s->userdata;
|
||||
audio_info_t info;
|
||||
int err;
|
||||
|
||||
err = ioctl(u->fd, AUDIO_GETINFO, &info);
|
||||
assert(err >= 0);
|
||||
|
||||
pa_cvolume_set(&s->hw_volume, s->hw_volume.channels,
|
||||
info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sink_set_hw_volume_cb(pa_sink *s) {
|
||||
struct userdata *u = s->userdata;
|
||||
audio_info_t info;
|
||||
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
info.play.gain = pa_cvolume_avg(&s->hw_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
|
||||
assert(info.play.gain <= AUDIO_MAX_GAIN);
|
||||
|
||||
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
|
||||
if (errno == EINVAL)
|
||||
pa_log("AUDIO_SETINFO: Unsupported volume.");
|
||||
else
|
||||
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sink_get_hw_mute_cb(pa_sink *s) {
|
||||
struct userdata *u = s->userdata;
|
||||
audio_info_t info;
|
||||
int err;
|
||||
|
||||
err = ioctl(u->fd, AUDIO_GETINFO, &info);
|
||||
assert(err >= 0);
|
||||
|
||||
s->hw_muted = !!info.output_muted;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sink_set_hw_mute_cb(pa_sink *s) {
|
||||
struct userdata *u = s->userdata;
|
||||
audio_info_t info;
|
||||
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
info.output_muted = !!s->hw_muted;
|
||||
|
||||
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
|
||||
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int source_get_hw_volume_cb(pa_source *s) {
|
||||
struct userdata *u = s->userdata;
|
||||
audio_info_t info;
|
||||
int err;
|
||||
|
||||
err = ioctl(u->fd, AUDIO_GETINFO, &info);
|
||||
assert(err >= 0);
|
||||
|
||||
pa_cvolume_set(&s->hw_volume, s->hw_volume.channels,
|
||||
info.record.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int source_set_hw_volume_cb(pa_source *s) {
|
||||
struct userdata *u = s->userdata;
|
||||
audio_info_t info;
|
||||
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
info.record.gain = pa_cvolume_avg(&s->hw_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
|
||||
assert(info.record.gain <= AUDIO_MAX_GAIN);
|
||||
|
||||
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
|
||||
if (errno == EINVAL)
|
||||
pa_log("AUDIO_SETINFO: Unsupported volume.");
|
||||
else
|
||||
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (u->source)
|
||||
pa_source_get_volume(u->source);
|
||||
}
|
||||
|
||||
static int pa_solaris_auto_format(int fd, int mode, pa_sample_spec *ss) {
|
||||
|
|
@ -490,6 +543,7 @@ static int pa_solaris_set_buffer(int fd, int buffer_size) {
|
|||
|
||||
AUDIO_INITINFO(&info);
|
||||
|
||||
info.play.buffer_size = buffer_size;
|
||||
info.record.buffer_size = buffer_size;
|
||||
|
||||
if (ioctl(fd, AUDIO_SETINFO, &info) < 0) {
|
||||
|
|
@ -503,7 +557,7 @@ static int pa_solaris_set_buffer(int fd, int buffer_size) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module *m) {
|
||||
struct userdata *u = NULL;
|
||||
const char *p;
|
||||
int fd = -1;
|
||||
|
|
@ -513,9 +567,10 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
pa_sample_spec ss;
|
||||
pa_channel_map map;
|
||||
pa_modargs *ma = NULL;
|
||||
struct timeval tv;
|
||||
char *t;
|
||||
assert(c && m);
|
||||
struct pollfd *pollfd;
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("failed to parse module arguments.");
|
||||
|
|
@ -540,7 +595,7 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
ss = c->default_sample_spec;
|
||||
ss = m->core->default_sample_spec;
|
||||
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) {
|
||||
pa_log("failed to parse sample specification");
|
||||
goto fail;
|
||||
|
|
@ -554,55 +609,18 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
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;
|
||||
if (pa_solaris_set_buffer(fd, buffer_size) < 0)
|
||||
goto fail;
|
||||
|
||||
u = pa_xmalloc(sizeof(struct userdata));
|
||||
u->core = c;
|
||||
u->core = m->core;
|
||||
|
||||
if (mode != O_WRONLY) {
|
||||
u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map);
|
||||
assert(u->source);
|
||||
u->source->userdata = u;
|
||||
u->source->get_latency = source_get_latency_cb;
|
||||
u->source->get_hw_volume = source_get_hw_volume_cb;
|
||||
u->source->set_hw_volume = source_set_hw_volume_cb;
|
||||
pa_source_set_owner(u->source, m);
|
||||
pa_source_set_description(u->source, t = pa_sprintf_malloc("Solaris PCM on '%s'", p));
|
||||
pa_xfree(t);
|
||||
u->source->is_hardware = 1;
|
||||
} 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, &map);
|
||||
assert(u->sink);
|
||||
u->sink->get_latency = sink_get_latency_cb;
|
||||
u->sink->get_hw_volume = sink_get_hw_volume_cb;
|
||||
u->sink->set_hw_volume = sink_set_hw_volume_cb;
|
||||
u->sink->get_hw_mute = sink_get_hw_mute_cb;
|
||||
u->sink->set_hw_mute = sink_set_hw_mute_cb;
|
||||
u->sink->userdata = u;
|
||||
pa_sink_set_owner(u->sink, m);
|
||||
pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Solaris PCM on '%s'", p));
|
||||
pa_xfree(t);
|
||||
u->sink->is_hardware = 1;
|
||||
} 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;
|
||||
pa_memchunk_reset(&u->memchunk);
|
||||
|
||||
/* We use this to get a reasonable chunk size */
|
||||
u->page_size = sysconf(_SC_PAGESIZE);
|
||||
u->page_size = PA_PAGE_SIZE;
|
||||
|
||||
u->frame_size = pa_frame_size(&ss);
|
||||
u->buffer_size = buffer_size;
|
||||
|
|
@ -610,37 +628,91 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
u->written_bytes = 0;
|
||||
u->read_bytes = 0;
|
||||
|
||||
u->sink_underflow = 1;
|
||||
|
||||
u->module = m;
|
||||
m->userdata = u;
|
||||
|
||||
u->poll_timeout = pa_bytes_to_usec(u->buffer_size / 10, &ss);
|
||||
pa_thread_mq_init(&u->thread_mq, m->core->mainloop);
|
||||
|
||||
pa_gettimeofday(&tv);
|
||||
pa_timeval_add(&tv, u->poll_timeout);
|
||||
u->rtpoll = pa_rtpoll_new();
|
||||
pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq);
|
||||
|
||||
u->timer = c->mainloop->time_new(c->mainloop, &tv, timer_cb, u);
|
||||
assert(u->timer);
|
||||
pa_rtpoll_set_timer_periodic(u->rtpoll, pa_bytes_to_usec(u->buffer_size / 10, &ss));
|
||||
|
||||
u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1);
|
||||
pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL);
|
||||
pollfd->fd = fd;
|
||||
pollfd->events = 0;
|
||||
pollfd->revents = 0;
|
||||
|
||||
if (mode != O_WRONLY) {
|
||||
u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map);
|
||||
pa_assert(u->source);
|
||||
|
||||
u->source->userdata = u;
|
||||
u->source->parent.process_msg = source_process_msg;
|
||||
|
||||
pa_source_set_module(u->source, m);
|
||||
pa_source_set_description(u->source, t = pa_sprintf_malloc("Solaris PCM on '%s'", p));
|
||||
pa_xfree(t);
|
||||
pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
|
||||
pa_source_set_rtpoll(u->source, u->rtpoll);
|
||||
|
||||
u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|PA_SOURCE_HW_VOLUME_CTRL;
|
||||
u->source->refresh_volume = 1;
|
||||
} else
|
||||
u->source = NULL;
|
||||
|
||||
if (mode != O_RDONLY) {
|
||||
u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map);
|
||||
pa_assert(u->sink);
|
||||
|
||||
u->sink->userdata = u;
|
||||
u->sink->parent.process_msg = sink_process_msg;
|
||||
|
||||
pa_sink_set_module(u->sink, m);
|
||||
pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Solaris PCM on '%s'", p));
|
||||
pa_xfree(t);
|
||||
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
|
||||
pa_sink_set_rtpoll(u->sink, u->rtpoll);
|
||||
|
||||
u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL;
|
||||
u->sink->refresh_volume = 1;
|
||||
u->sink->refresh_mute = 1;
|
||||
} else
|
||||
u->sink = NULL;
|
||||
|
||||
pa_assert(u->source || u->sink);
|
||||
|
||||
u->sig = pa_signal_new(SIGPOLL, sig_callback, u);
|
||||
assert(u->sig);
|
||||
pa_assert(u->sig);
|
||||
ioctl(u->fd, I_SETSIG, S_MSG);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
if (!(u->thread = pa_thread_new(thread_func, u))) {
|
||||
pa_log("Failed to create thread.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Read mixer settings */
|
||||
if (u->source)
|
||||
source_get_hw_volume_cb(u->source);
|
||||
pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->source), PA_SOURCE_MESSAGE_GET_VOLUME, &u->source->volume, 0, NULL);
|
||||
if (u->sink) {
|
||||
sink_get_hw_volume_cb(u->sink);
|
||||
sink_get_hw_mute_cb(u->sink);
|
||||
pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_GET_VOLUME, &u->sink->volume, 0, NULL);
|
||||
pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_GET_MUTE, &u->sink->muted, 0, NULL);
|
||||
}
|
||||
|
||||
if (u->sink)
|
||||
pa_sink_put(u->sink);
|
||||
if (u->source)
|
||||
pa_source_put(u->source);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (fd >= 0)
|
||||
if (u)
|
||||
pa__done(m);
|
||||
else if (fd >= 0)
|
||||
close(fd);
|
||||
|
||||
if (ma)
|
||||
|
|
@ -649,31 +721,47 @@ fail:
|
|||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module *m) {
|
||||
struct userdata *u;
|
||||
assert(c && m);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
||||
if (u->timer)
|
||||
c->mainloop->time_free(u->timer);
|
||||
ioctl(u->fd, I_SETSIG, 0);
|
||||
pa_signal_free(u->sig);
|
||||
|
||||
if (u->memchunk.memblock)
|
||||
if (u->sink)
|
||||
pa_sink_unlink(u->sink);
|
||||
|
||||
if (u->source)
|
||||
pa_source_unlink(u->source);
|
||||
|
||||
if (u->thread) {
|
||||
pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
|
||||
pa_thread_free(u->thread);
|
||||
}
|
||||
|
||||
pa_thread_mq_done(&u->thread_mq);
|
||||
|
||||
if (u->sink)
|
||||
pa_sink_unref(u->sink);
|
||||
|
||||
if (u->source)
|
||||
pa_source_unref(u->source);
|
||||
|
||||
if (u->memchunk.memblock)
|
||||
pa_memblock_unref(u->memchunk.memblock);
|
||||
|
||||
if (u->sink) {
|
||||
pa_sink_disconnect(u->sink);
|
||||
pa_sink_unref(u->sink);
|
||||
}
|
||||
if (u->rtpoll_item)
|
||||
pa_rtpoll_item_free(u->rtpoll_item);
|
||||
|
||||
if (u->source) {
|
||||
pa_source_disconnect(u->source);
|
||||
pa_source_unref(u->source);
|
||||
}
|
||||
if (u->rtpoll)
|
||||
pa_rtpoll_free(u->rtpoll);
|
||||
|
||||
if (u->fd >= 0)
|
||||
close(u->fd);
|
||||
|
||||
pa_iochannel_free(u->io);
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
|
|
|||
473
src/modules/module-suspend-on-idle.c
Normal file
473
src/modules/module-suspend-on-idle.c
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
/* $Id$ */
|
||||
|
||||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2006 Lennart Poettering
|
||||
|
||||
PulseAudio 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.
|
||||
|
||||
PulseAudio 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 PulseAudio; 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 <pulse/xmalloc.h>
|
||||
#include <pulse/timeval.h>
|
||||
|
||||
#include <pulsecore/core.h>
|
||||
#include <pulsecore/sink-input.h>
|
||||
#include <pulsecore/source-output.h>
|
||||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/namereg.h>
|
||||
|
||||
#include "module-suspend-on-idle-symdef.h"
|
||||
|
||||
PA_MODULE_AUTHOR("Lennart Poettering")
|
||||
PA_MODULE_DESCRIPTION("When a sink/source is idle for too long, suspend it")
|
||||
PA_MODULE_VERSION(PACKAGE_VERSION)
|
||||
|
||||
static const char* const valid_modargs[] = {
|
||||
"timeout",
|
||||
NULL,
|
||||
};
|
||||
|
||||
struct userdata {
|
||||
pa_core *core;
|
||||
pa_usec_t timeout;
|
||||
pa_hashmap *device_infos;
|
||||
pa_hook_slot
|
||||
*sink_new_slot,
|
||||
*source_new_slot,
|
||||
*sink_unlink_slot,
|
||||
*source_unlink_slot,
|
||||
*sink_state_changed_slot,
|
||||
*source_state_changed_slot;
|
||||
|
||||
pa_hook_slot
|
||||
*sink_input_new_slot,
|
||||
*source_output_new_slot,
|
||||
*sink_input_unlink_slot,
|
||||
*source_output_unlink_slot,
|
||||
*sink_input_move_slot,
|
||||
*source_output_move_slot,
|
||||
*sink_input_move_post_slot,
|
||||
*source_output_move_post_slot,
|
||||
*sink_input_state_changed_slot,
|
||||
*source_output_state_changed_slot;
|
||||
};
|
||||
|
||||
struct device_info {
|
||||
struct userdata *userdata;
|
||||
pa_sink *sink;
|
||||
pa_source *source;
|
||||
struct timeval last_use;
|
||||
pa_time_event *time_event;
|
||||
};
|
||||
|
||||
static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) {
|
||||
struct device_info *d = userdata;
|
||||
|
||||
pa_assert(d);
|
||||
|
||||
d->userdata->core->mainloop->time_restart(d->time_event, NULL);
|
||||
|
||||
if (d->sink && pa_sink_used_by(d->sink) <= 0 && pa_sink_get_state(d->sink) != PA_SINK_SUSPENDED) {
|
||||
pa_log_info("Sink %s idle for too long, suspending ...", d->sink->name);
|
||||
pa_sink_suspend(d->sink, TRUE);
|
||||
}
|
||||
|
||||
if (d->source && pa_source_used_by(d->source) <= 0 && pa_source_get_state(d->source) != PA_SOURCE_SUSPENDED) {
|
||||
pa_log_info("Source %s idle for too long, suspending ...", d->source->name);
|
||||
pa_source_suspend(d->source, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
static void restart(struct device_info *d) {
|
||||
struct timeval tv;
|
||||
pa_assert(d);
|
||||
|
||||
pa_gettimeofday(&tv);
|
||||
d->last_use = tv;
|
||||
pa_timeval_add(&tv, d->userdata->timeout*1000000);
|
||||
d->userdata->core->mainloop->time_restart(d->time_event, &tv);
|
||||
|
||||
if (d->sink)
|
||||
pa_log_debug("Sink %s becomes idle.", d->sink->name);
|
||||
if (d->source)
|
||||
pa_log_debug("Source %s becomes idle.", d->source->name);
|
||||
}
|
||||
|
||||
static void resume(struct device_info *d) {
|
||||
pa_assert(d);
|
||||
|
||||
d->userdata->core->mainloop->time_restart(d->time_event, NULL);
|
||||
|
||||
if (d->sink) {
|
||||
pa_sink_suspend(d->sink, FALSE);
|
||||
|
||||
pa_log_debug("Sink %s becomes busy.", d->sink->name);
|
||||
}
|
||||
|
||||
if (d->source) {
|
||||
pa_source_suspend(d->source, FALSE);
|
||||
|
||||
pa_log_debug("Source %s becomes busy.", d->source->name);
|
||||
}
|
||||
}
|
||||
|
||||
static pa_hook_result_t sink_input_new_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) {
|
||||
struct device_info *d;
|
||||
|
||||
pa_assert(c);
|
||||
pa_sink_input_assert_ref(s);
|
||||
pa_assert(u);
|
||||
|
||||
if ((d = pa_hashmap_get(u->device_infos, s->sink)))
|
||||
resume(d);
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static pa_hook_result_t source_output_new_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) {
|
||||
struct device_info *d;
|
||||
|
||||
pa_assert(c);
|
||||
pa_source_output_assert_ref(s);
|
||||
pa_assert(u);
|
||||
|
||||
if ((d = pa_hashmap_get(u->device_infos, s->source)))
|
||||
resume(d);
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static pa_hook_result_t sink_input_unlink_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) {
|
||||
pa_assert(c);
|
||||
pa_sink_input_assert_ref(s);
|
||||
pa_assert(u);
|
||||
|
||||
if (pa_sink_used_by(s->sink) <= 0) {
|
||||
struct device_info *d;
|
||||
if ((d = pa_hashmap_get(u->device_infos, s->sink)))
|
||||
restart(d);
|
||||
}
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static pa_hook_result_t source_output_unlink_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) {
|
||||
pa_assert(c);
|
||||
pa_source_output_assert_ref(s);
|
||||
pa_assert(u);
|
||||
|
||||
if (pa_source_used_by(s->source) <= 0) {
|
||||
struct device_info *d;
|
||||
if ((d = pa_hashmap_get(u->device_infos, s->source)))
|
||||
restart(d);
|
||||
}
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static pa_hook_result_t sink_input_move_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) {
|
||||
pa_assert(c);
|
||||
pa_sink_input_assert_ref(s);
|
||||
pa_assert(u);
|
||||
|
||||
if (pa_sink_used_by(s->sink) <= 1) {
|
||||
struct device_info *d;
|
||||
if ((d = pa_hashmap_get(u->device_infos, s->sink)))
|
||||
restart(d);
|
||||
}
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static pa_hook_result_t sink_input_move_post_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) {
|
||||
struct device_info *d;
|
||||
pa_assert(c);
|
||||
pa_sink_input_assert_ref(s);
|
||||
pa_assert(u);
|
||||
|
||||
if ((d = pa_hashmap_get(u->device_infos, s->sink)))
|
||||
resume(d);
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static pa_hook_result_t source_output_move_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) {
|
||||
pa_assert(c);
|
||||
pa_source_output_assert_ref(s);
|
||||
pa_assert(u);
|
||||
|
||||
if (pa_source_used_by(s->source) <= 1) {
|
||||
struct device_info *d;
|
||||
|
||||
if ((d = pa_hashmap_get(u->device_infos, s->source)))
|
||||
restart(d);
|
||||
}
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static pa_hook_result_t source_output_move_post_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) {
|
||||
struct device_info *d;
|
||||
pa_assert(c);
|
||||
pa_source_output_assert_ref(s);
|
||||
pa_assert(u);
|
||||
|
||||
if ((d = pa_hashmap_get(u->device_infos, s->source)))
|
||||
resume(d);
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) {
|
||||
struct device_info *d;
|
||||
pa_sink_input_state_t state;
|
||||
pa_assert(c);
|
||||
pa_sink_input_assert_ref(s);
|
||||
pa_assert(u);
|
||||
|
||||
state = pa_sink_input_get_state(s);
|
||||
if (state == PA_SINK_INPUT_RUNNING || state == PA_SINK_INPUT_DRAINED)
|
||||
if ((d = pa_hashmap_get(u->device_infos, s->sink)))
|
||||
resume(d);
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static pa_hook_result_t source_output_state_changed_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) {
|
||||
struct device_info *d;
|
||||
pa_source_output_state_t state;
|
||||
pa_assert(c);
|
||||
pa_source_output_assert_ref(s);
|
||||
pa_assert(u);
|
||||
|
||||
state = pa_source_output_get_state(s);
|
||||
if (state == PA_SOURCE_OUTPUT_RUNNING)
|
||||
if ((d = pa_hashmap_get(u->device_infos, s->source)))
|
||||
resume(d);
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static pa_hook_result_t device_new_hook_cb(pa_core *c, pa_object *o, struct userdata *u) {
|
||||
struct device_info *d;
|
||||
pa_source *source;
|
||||
pa_sink *sink;
|
||||
|
||||
pa_assert(c);
|
||||
pa_object_assert_ref(o);
|
||||
pa_assert(u);
|
||||
|
||||
source = pa_source_isinstance(o) ? PA_SOURCE(o) : NULL;
|
||||
sink = pa_sink_isinstance(o) ? PA_SINK(o) : NULL;
|
||||
|
||||
pa_assert(source || sink);
|
||||
|
||||
d = pa_xnew(struct device_info, 1);
|
||||
d->userdata = u;
|
||||
d->source = source ? pa_source_ref(source) : NULL;
|
||||
d->sink = sink ? pa_sink_ref(sink) : NULL;
|
||||
d->time_event = c->mainloop->time_new(c->mainloop, NULL, timeout_cb, d);
|
||||
pa_hashmap_put(u->device_infos, o, d);
|
||||
|
||||
if ((d->sink && pa_sink_used_by(d->sink) <= 0) ||
|
||||
(d->source && pa_source_used_by(d->source) <= 0))
|
||||
restart(d);
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static void device_info_free(struct device_info *d) {
|
||||
pa_assert(d);
|
||||
|
||||
if (d->source)
|
||||
pa_source_unref(d->source);
|
||||
if (d->sink)
|
||||
pa_sink_unref(d->sink);
|
||||
|
||||
d->userdata->core->mainloop->time_free(d->time_event);
|
||||
|
||||
pa_xfree(d);
|
||||
}
|
||||
|
||||
static pa_hook_result_t device_unlink_hook_cb(pa_core *c, pa_object *o, struct userdata *u) {
|
||||
struct device_info *d;
|
||||
|
||||
pa_assert(c);
|
||||
pa_object_assert_ref(o);
|
||||
pa_assert(u);
|
||||
|
||||
if ((d = pa_hashmap_remove(u->device_infos, o)))
|
||||
device_info_free(d);
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, struct userdata *u) {
|
||||
struct device_info *d;
|
||||
|
||||
pa_assert(c);
|
||||
pa_object_assert_ref(o);
|
||||
pa_assert(u);
|
||||
|
||||
if (!(d = pa_hashmap_get(u->device_infos, o)))
|
||||
return PA_HOOK_OK;
|
||||
|
||||
if (pa_sink_isinstance(o)) {
|
||||
pa_sink *s = PA_SINK(o);
|
||||
pa_sink_state_t state = pa_sink_get_state(s);
|
||||
|
||||
if (pa_sink_used_by(s) <= 0) {
|
||||
|
||||
if (PA_SINK_OPENED(state))
|
||||
restart(d);
|
||||
|
||||
}
|
||||
|
||||
} else if (pa_source_isinstance(o)) {
|
||||
pa_source *s = PA_SOURCE(o);
|
||||
pa_source_state_t state = pa_source_get_state(s);
|
||||
|
||||
if (pa_source_used_by(s) <= 0) {
|
||||
|
||||
if (PA_SOURCE_OPENED(state))
|
||||
restart(d);
|
||||
}
|
||||
}
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
int pa__init(pa_module*m) {
|
||||
pa_modargs *ma = NULL;
|
||||
struct userdata *u;
|
||||
uint32_t timeout = 1;
|
||||
uint32_t idx;
|
||||
pa_sink *sink;
|
||||
pa_source *source;
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("Failed to parse module arguments.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (pa_modargs_get_value_u32(ma, "timeout", &timeout) < 0) {
|
||||
pa_log("Failed to parse timeout value.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
m->userdata = u = pa_xnew(struct userdata, 1);
|
||||
u->core = m->core;
|
||||
u->timeout = timeout;
|
||||
u->device_infos = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
|
||||
|
||||
for (sink = pa_idxset_first(m->core->sinks, &idx); sink; sink = pa_idxset_next(m->core->sinks, &idx))
|
||||
device_new_hook_cb(m->core, PA_OBJECT(sink), u);
|
||||
|
||||
for (source = pa_idxset_first(m->core->sources, &idx); source; source = pa_idxset_next(m->core->sources, &idx))
|
||||
device_new_hook_cb(m->core, PA_OBJECT(source), u);
|
||||
|
||||
u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) device_new_hook_cb, u);
|
||||
u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], (pa_hook_cb_t) device_new_hook_cb, u);
|
||||
u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], (pa_hook_cb_t) device_unlink_hook_cb, u);
|
||||
u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK_POST], (pa_hook_cb_t) device_unlink_hook_cb, u);
|
||||
u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], (pa_hook_cb_t) device_state_changed_hook_cb, u);
|
||||
u->source_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], (pa_hook_cb_t) device_state_changed_hook_cb, u);
|
||||
|
||||
u->sink_input_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], (pa_hook_cb_t) sink_input_new_hook_cb, u);
|
||||
u->source_output_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], (pa_hook_cb_t) source_output_new_hook_cb, u);
|
||||
u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], (pa_hook_cb_t) sink_input_unlink_hook_cb, u);
|
||||
u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], (pa_hook_cb_t) source_output_unlink_hook_cb, u);
|
||||
u->sink_input_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE], (pa_hook_cb_t) sink_input_move_hook_cb, u);
|
||||
u->source_output_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE], (pa_hook_cb_t) source_output_move_hook_cb, u);
|
||||
u->sink_input_move_post_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_POST], (pa_hook_cb_t) sink_input_move_post_hook_cb, u);
|
||||
u->source_output_move_post_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_POST], (pa_hook_cb_t) source_output_move_post_hook_cb, u);
|
||||
u->sink_input_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], (pa_hook_cb_t) sink_input_state_changed_hook_cb, u);
|
||||
u->source_output_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], (pa_hook_cb_t) source_output_state_changed_hook_cb, u);
|
||||
|
||||
|
||||
pa_modargs_free(ma);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
|
||||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
struct device_info *d;
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!m->userdata)
|
||||
return;
|
||||
|
||||
u = m->userdata;
|
||||
|
||||
if (u->sink_new_slot)
|
||||
pa_hook_slot_free(u->sink_new_slot);
|
||||
if (u->sink_unlink_slot)
|
||||
pa_hook_slot_free(u->sink_unlink_slot);
|
||||
if (u->sink_state_changed_slot)
|
||||
pa_hook_slot_free(u->sink_state_changed_slot);
|
||||
|
||||
if (u->source_new_slot)
|
||||
pa_hook_slot_free(u->source_new_slot);
|
||||
if (u->source_unlink_slot)
|
||||
pa_hook_slot_free(u->source_unlink_slot);
|
||||
if (u->source_state_changed_slot)
|
||||
pa_hook_slot_free(u->source_state_changed_slot);
|
||||
|
||||
if (u->sink_input_new_slot)
|
||||
pa_hook_slot_free(u->sink_input_new_slot);
|
||||
if (u->sink_input_unlink_slot)
|
||||
pa_hook_slot_free(u->sink_input_unlink_slot);
|
||||
if (u->sink_input_move_slot)
|
||||
pa_hook_slot_free(u->sink_input_move_slot);
|
||||
if (u->sink_input_move_post_slot)
|
||||
pa_hook_slot_free(u->sink_input_move_post_slot);
|
||||
if (u->sink_input_state_changed_slot)
|
||||
pa_hook_slot_free(u->sink_input_state_changed_slot);
|
||||
|
||||
if (u->source_output_new_slot)
|
||||
pa_hook_slot_free(u->source_output_new_slot);
|
||||
if (u->source_output_unlink_slot)
|
||||
pa_hook_slot_free(u->source_output_unlink_slot);
|
||||
if (u->source_output_move_slot)
|
||||
pa_hook_slot_free(u->source_output_move_slot);
|
||||
if (u->source_output_move_post_slot)
|
||||
pa_hook_slot_free(u->source_output_move_post_slot);
|
||||
if (u->source_output_state_changed_slot)
|
||||
pa_hook_slot_free(u->source_output_state_changed_slot);
|
||||
|
||||
while ((d = pa_hashmap_steal_first(u->device_infos)))
|
||||
device_info_free(d);
|
||||
|
||||
pa_hashmap_free(u->device_infos, NULL, NULL);
|
||||
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
|
@ -596,12 +596,12 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t
|
|||
}
|
||||
|
||||
#ifdef TUNNEL_SINK
|
||||
snprintf(name, sizeof(name), "Tunnel from host %s, user %s, sink %s",
|
||||
pa_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_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);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@
|
|||
#endif
|
||||
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
|
|
@ -35,6 +34,7 @@
|
|||
#include <ctype.h>
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
#include <pulse/volume.h>
|
||||
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/module.h>
|
||||
|
|
@ -44,9 +44,7 @@
|
|||
#include <pulsecore/core-subscribe.h>
|
||||
#include <pulsecore/sink-input.h>
|
||||
#include <pulsecore/source-output.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/namereg.h>
|
||||
#include <pulse/volume.h>
|
||||
|
||||
#include "module-volume-restore-symdef.h"
|
||||
|
||||
|
|
@ -85,8 +83,8 @@ static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) {
|
|||
long k;
|
||||
unsigned i;
|
||||
|
||||
assert(s);
|
||||
assert(v);
|
||||
pa_assert(s);
|
||||
pa_assert(v);
|
||||
|
||||
if (!isdigit(*s))
|
||||
return NULL;
|
||||
|
|
@ -170,7 +168,7 @@ static int load_rules(struct userdata *u) {
|
|||
continue;
|
||||
}
|
||||
|
||||
assert(ln == buf_source);
|
||||
pa_assert(ln == buf_source);
|
||||
|
||||
if (buf_volume[0]) {
|
||||
if (!parse_volume(buf_volume, &v)) {
|
||||
|
|
@ -297,8 +295,8 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
|
|||
struct rule *r;
|
||||
char *name;
|
||||
|
||||
assert(c);
|
||||
assert(u);
|
||||
pa_assert(c);
|
||||
pa_assert(u);
|
||||
|
||||
if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) &&
|
||||
t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) &&
|
||||
|
|
@ -313,7 +311,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
|
|||
if (!si->client || !(name = client_name(si->client)))
|
||||
return;
|
||||
} else {
|
||||
assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT);
|
||||
pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT);
|
||||
|
||||
if (!(so = pa_idxset_get_by_index(c->source_outputs, idx)))
|
||||
return;
|
||||
|
|
@ -341,7 +339,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
|
|||
u->modified = 1;
|
||||
}
|
||||
} else {
|
||||
assert(so);
|
||||
pa_assert(so);
|
||||
|
||||
if (!r->source || strcmp(so->source->name, r->source) != 0) {
|
||||
pa_log_info("Saving source for <%s>", r->name);
|
||||
|
|
@ -363,7 +361,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
|
|||
r->sink = pa_xstrdup(si->sink->name);
|
||||
r->source = NULL;
|
||||
} else {
|
||||
assert(so);
|
||||
pa_assert(so);
|
||||
r->volume_is_set = 0;
|
||||
r->sink = NULL;
|
||||
r->source = pa_xstrdup(so->source->name);
|
||||
|
|
@ -378,7 +376,7 @@ static pa_hook_result_t sink_input_hook_callback(pa_core *c, pa_sink_input_new_d
|
|||
struct rule *r;
|
||||
char *name;
|
||||
|
||||
assert(data);
|
||||
pa_assert(data);
|
||||
|
||||
if (!data->client || !(name = client_name(data->client)))
|
||||
return PA_HOOK_OK;
|
||||
|
|
@ -396,6 +394,8 @@ static pa_hook_result_t sink_input_hook_callback(pa_core *c, pa_sink_input_new_d
|
|||
}
|
||||
}
|
||||
|
||||
pa_xfree(name);
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
|
|
@ -403,7 +403,7 @@ static pa_hook_result_t source_output_hook_callback(pa_core *c, pa_source_output
|
|||
struct rule *r;
|
||||
char *name;
|
||||
|
||||
assert(data);
|
||||
pa_assert(data);
|
||||
|
||||
if (!data->client || !(name = client_name(data->client)))
|
||||
return PA_HOOK_OK;
|
||||
|
|
@ -418,12 +418,11 @@ static pa_hook_result_t source_output_hook_callback(pa_core *c, pa_source_output
|
|||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
pa_modargs *ma = NULL;
|
||||
struct userdata *u;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("Failed to parse module arguments");
|
||||
|
|
@ -442,16 +441,15 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
if (load_rules(u) < 0)
|
||||
goto fail;
|
||||
|
||||
u->subscription = pa_subscription_new(c, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u);
|
||||
u->sink_input_hook_slot = pa_hook_connect(&c->hook_sink_input_new, (pa_hook_cb_t) sink_input_hook_callback, u);
|
||||
u->source_output_hook_slot = pa_hook_connect(&c->hook_source_output_new, (pa_hook_cb_t) source_output_hook_callback, u);
|
||||
u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u);
|
||||
u->sink_input_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], (pa_hook_cb_t) sink_input_hook_callback, u);
|
||||
u->source_output_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], (pa_hook_cb_t) source_output_hook_callback, u);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
pa__done(c, m);
|
||||
|
||||
pa__done(m);
|
||||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
|
|
@ -460,7 +458,7 @@ fail:
|
|||
|
||||
static void free_func(void *p, void *userdata) {
|
||||
struct rule *r = p;
|
||||
assert(r);
|
||||
pa_assert(r);
|
||||
|
||||
pa_xfree(r->name);
|
||||
pa_xfree(r->sink);
|
||||
|
|
@ -468,11 +466,10 @@ static void free_func(void *p, void *userdata) {
|
|||
pa_xfree(r);
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata* u;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@
|
|||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
|
@ -67,30 +66,21 @@ static const char* const valid_modargs[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
static int ring_bell(struct userdata *u, int percent) {
|
||||
pa_sink *s;
|
||||
assert(u);
|
||||
|
||||
if (!(s = pa_namereg_get(u->core, u->sink_name, PA_NAMEREG_SINK, 1))) {
|
||||
pa_log("Invalid sink: %s", u->sink_name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pa_scache_play_item(u->core, u->scache_item, s, (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);
|
||||
|
||||
pa_assert(w);
|
||||
pa_assert(e);
|
||||
pa_assert(u);
|
||||
pa_assert(u->x11_wrapper == w);
|
||||
|
||||
if (((XkbEvent*) e)->any.xkb_type != XkbBellNotify)
|
||||
return 0;
|
||||
|
||||
bne = (XkbBellNotifyEvent*) e;
|
||||
|
||||
if (ring_bell(u, bne->percent) < 0) {
|
||||
if (pa_scache_play_item_by_name(u->core, u->scache_item, u->sink_name, (bne->percent*PA_VOLUME_NORM)/100, 1) < 0) {
|
||||
pa_log_info("Ringing bell failed, reverting to X11 device bell.");
|
||||
XkbForceDeviceBell(pa_x11_wrapper_get_display(w), bne->device, bne->bell_class, bne->bell_id, bne->percent);
|
||||
}
|
||||
|
|
@ -98,25 +88,27 @@ static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
|
||||
struct userdata *u = NULL;
|
||||
pa_modargs *ma = NULL;
|
||||
int major, minor;
|
||||
unsigned int auto_ctrls, auto_values;
|
||||
assert(c && m);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("failed to parse module arguments");
|
||||
pa_log("Failed to parse module arguments");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
m->userdata = u = pa_xmalloc(sizeof(struct userdata));
|
||||
u->core = c;
|
||||
m->userdata = u = pa_xnew(struct userdata, 1);
|
||||
u->core = m->core;
|
||||
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))))
|
||||
if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL))))
|
||||
goto fail;
|
||||
|
||||
major = XkbMajorVersion;
|
||||
|
|
@ -130,7 +122,6 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
major = XkbMajorVersion;
|
||||
minor = XkbMinorVersion;
|
||||
|
||||
|
||||
if (!XkbQueryExtension(pa_x11_wrapper_get_display(u->x11_wrapper), NULL, &u->xkb_event_base, NULL, &major, &minor)) {
|
||||
pa_log("XkbQueryExtension() failed");
|
||||
goto fail;
|
||||
|
|
@ -150,14 +141,21 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
fail:
|
||||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
if (m->userdata)
|
||||
pa__done(c, m);
|
||||
|
||||
pa__done(m);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
struct userdata *u = m->userdata;
|
||||
assert(c && m && u);
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!m->userdata)
|
||||
return;
|
||||
|
||||
u = m->userdata;
|
||||
|
||||
pa_xfree(u->scache_item);
|
||||
pa_xfree(u->sink_name);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@
|
|||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
|
@ -76,7 +75,7 @@ struct userdata {
|
|||
};
|
||||
|
||||
static int load_key(struct userdata *u, const char*fn) {
|
||||
assert(u);
|
||||
pa_assert(u);
|
||||
|
||||
u->auth_cookie_in_property = 0;
|
||||
|
||||
|
|
@ -93,7 +92,7 @@ static int load_key(struct userdata *u, const char*fn) {
|
|||
if (pa_authkey_load_auto(fn, u->auth_cookie, sizeof(u->auth_cookie)) < 0)
|
||||
return -1;
|
||||
|
||||
pa_log_debug("loading cookie from disk.");
|
||||
pa_log_debug("Loading cookie from disk.");
|
||||
|
||||
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;
|
||||
|
|
@ -101,7 +100,7 @@ static int load_key(struct userdata *u, const char*fn) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
struct userdata *u;
|
||||
pa_modargs *ma = NULL;
|
||||
char hn[256], un[128];
|
||||
|
|
@ -110,23 +109,25 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
char *s;
|
||||
pa_strlist *l;
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("failed to parse module arguments");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
m->userdata = u = pa_xmalloc(sizeof(struct userdata));
|
||||
u->core = c;
|
||||
u->core = m->core;
|
||||
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))))
|
||||
if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL))))
|
||||
goto fail;
|
||||
|
||||
if (!(l = pa_property_get(c, PA_NATIVE_SERVER_PROPERTY_NAME)))
|
||||
if (!(l = pa_property_get(m->core, PA_NATIVE_SERVER_PROPERTY_NAME)))
|
||||
goto fail;
|
||||
|
||||
s = pa_strlist_tostring(l);
|
||||
|
|
@ -154,13 +155,14 @@ fail:
|
|||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
pa__done(c, m);
|
||||
pa__done(m);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata*u;
|
||||
assert(c && m);
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
|
@ -185,7 +187,7 @@ void pa__done(pa_core *c, pa_module*m) {
|
|||
pa_x11_wrapper_unref(u->x11_wrapper);
|
||||
|
||||
if (u->auth_cookie_in_property)
|
||||
pa_authkey_prop_unref(c, PA_NATIVE_COOKIE_PROPERTY_NAME);
|
||||
pa_authkey_prop_unref(m->core, PA_NATIVE_COOKIE_PROPERTY_NAME);
|
||||
|
||||
pa_xfree(u->id);
|
||||
pa_xfree(u);
|
||||
|
|
|
|||
195
src/modules/module-x11-xsmp.c
Normal file
195
src/modules/module-x11-xsmp.c
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/* $Id$ */
|
||||
|
||||
/***
|
||||
This file is part of PulseAudio.
|
||||
|
||||
Copyright 2004-2006 Lennart Poettering
|
||||
|
||||
PulseAudio 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.
|
||||
|
||||
PulseAudio 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 PulseAudio; 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/SM/SMlib.h>
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
#include <pulse/util.h>
|
||||
|
||||
#include <pulsecore/iochannel.h>
|
||||
#include <pulsecore/sink.h>
|
||||
#include <pulsecore/core-scache.h>
|
||||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/namereg.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
|
||||
#include "module-x11-xsmp-symdef.h"
|
||||
|
||||
PA_MODULE_AUTHOR("Lennart Poettering")
|
||||
PA_MODULE_DESCRIPTION("X11 session management")
|
||||
PA_MODULE_VERSION(PACKAGE_VERSION)
|
||||
|
||||
static int ice_in_use = 0;
|
||||
|
||||
static const char* const valid_modargs[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static void die_cb(SmcConn connection, SmPointer client_data){
|
||||
pa_core *c = PA_CORE(client_data);
|
||||
|
||||
pa_log_debug("Got die message from XSM. Exiting...");
|
||||
|
||||
pa_core_assert_ref(c);
|
||||
c->mainloop->quit(c->mainloop, 0);
|
||||
}
|
||||
|
||||
static void save_complete_cb(SmcConn connection, SmPointer client_data) {
|
||||
}
|
||||
|
||||
static void shutdown_cancelled_cb(SmcConn connection, SmPointer client_data) {
|
||||
SmcSaveYourselfDone(connection, True);
|
||||
}
|
||||
|
||||
static void save_yourself_cb(SmcConn connection, SmPointer client_data, int save_type, Bool _shutdown, int interact_style, Bool fast) {
|
||||
SmcSaveYourselfDone(connection, True);
|
||||
}
|
||||
|
||||
static void ice_io_cb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) {
|
||||
IceConn connection = userdata;
|
||||
|
||||
if (IceProcessMessages(connection, NULL, NULL) == IceProcessMessagesIOError) {
|
||||
IceSetShutdownNegotiation(connection, False);
|
||||
IceCloseConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
static void new_ice_connection(IceConn connection, IcePointer client_data, Bool opening, IcePointer *watch_data) {
|
||||
pa_core *c = client_data;
|
||||
|
||||
pa_assert(c);
|
||||
|
||||
if (opening)
|
||||
*watch_data = c->mainloop->io_new(c->mainloop, IceConnectionNumber(connection), PA_IO_EVENT_INPUT, ice_io_cb, connection);
|
||||
else
|
||||
c->mainloop->io_free(*watch_data);
|
||||
}
|
||||
|
||||
int pa__init(pa_module*m) {
|
||||
|
||||
pa_modargs *ma = NULL;
|
||||
char t[256], *vendor, *client_id;
|
||||
SmcCallbacks callbacks;
|
||||
SmProp prop_program, prop_user;
|
||||
SmProp *prop_list[2];
|
||||
SmPropValue val_program, val_user;
|
||||
SmcConn connection;
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (ice_in_use) {
|
||||
pa_log("module-x11-xsmp may no be loaded twice.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
IceAddConnectionWatch(new_ice_connection, m->core);
|
||||
ice_in_use = 1;
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("Failed to parse module arguments");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!getenv("SESSION_MANAGER")) {
|
||||
pa_log("X11 session manager not running.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.die.callback = die_cb;
|
||||
callbacks.die.client_data = m->core;
|
||||
callbacks.save_yourself.callback = save_yourself_cb;
|
||||
callbacks.save_yourself.client_data = m->core;
|
||||
callbacks.save_complete.callback = save_complete_cb;
|
||||
callbacks.save_complete.client_data = m->core;
|
||||
callbacks.shutdown_cancelled.callback = shutdown_cancelled_cb;
|
||||
callbacks.shutdown_cancelled.client_data = m->core;
|
||||
|
||||
if (!(m->userdata = connection = SmcOpenConnection(
|
||||
NULL, m->core,
|
||||
SmProtoMajor, SmProtoMinor,
|
||||
SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask,
|
||||
&callbacks, NULL, &client_id,
|
||||
sizeof(t), t))) {
|
||||
|
||||
pa_log("Failed to open connection to session manager: %s", t);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
prop_program.name = (char*) SmProgram;
|
||||
prop_program.type = (char*) SmARRAY8;
|
||||
val_program.value = (char*) PACKAGE_NAME;
|
||||
val_program.length = strlen(val_program.value);
|
||||
prop_program.num_vals = 1;
|
||||
prop_program.vals = &val_program;
|
||||
prop_list[0] = &prop_program;
|
||||
|
||||
prop_user.name = (char*) SmUserID;
|
||||
prop_user.type = (char*) SmARRAY8;
|
||||
pa_get_user_name(t, sizeof(t));
|
||||
val_user.value = t;
|
||||
val_user.length = strlen(val_user.value);
|
||||
prop_user.num_vals = 1;
|
||||
prop_user.vals = &val_user;
|
||||
prop_list[1] = &prop_user;
|
||||
|
||||
SmcSetProperties(connection, PA_ELEMENTSOF(prop_list), prop_list);
|
||||
|
||||
pa_log_info("Connected to session manager '%s' as '%s'.", vendor = SmcVendor(connection), client_id);
|
||||
free(vendor);
|
||||
free(client_id);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
||||
pa__done(m);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_module*m) {
|
||||
pa_assert(m);
|
||||
|
||||
if (m->userdata)
|
||||
SmcCloseConnection(m->userdata, 0, NULL);
|
||||
|
||||
if (ice_in_use) {
|
||||
IceRemoveConnectionWatch(new_ice_connection, m->core);
|
||||
ice_in_use = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@
|
|||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
|
@ -35,11 +34,11 @@
|
|||
#include <avahi-client/publish.h>
|
||||
#include <avahi-common/alternative.h>
|
||||
#include <avahi-common/error.h>
|
||||
#include <avahi-common/domain.h>
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
#include <pulse/util.h>
|
||||
|
||||
#include <pulsecore/autoload.h>
|
||||
#include <pulsecore/sink.h>
|
||||
#include <pulsecore/source.h>
|
||||
#include <pulsecore/native-common.h>
|
||||
|
|
@ -71,56 +70,52 @@ struct service {
|
|||
struct userdata *userdata;
|
||||
AvahiEntryGroup *entry_group;
|
||||
char *service_name;
|
||||
char *name;
|
||||
enum { UNPUBLISHED, PUBLISHED_REAL, PUBLISHED_AUTOLOAD } published ;
|
||||
|
||||
struct {
|
||||
int valid;
|
||||
pa_namereg_type_t type;
|
||||
uint32_t index;
|
||||
} loaded;
|
||||
|
||||
struct {
|
||||
int valid;
|
||||
pa_namereg_type_t type;
|
||||
uint32_t index;
|
||||
} autoload;
|
||||
pa_object *device;
|
||||
};
|
||||
|
||||
struct userdata {
|
||||
pa_core *core;
|
||||
AvahiPoll *avahi_poll;
|
||||
AvahiClient *client;
|
||||
|
||||
pa_hashmap *services;
|
||||
pa_dynarray *sink_dynarray, *source_dynarray, *autoload_dynarray;
|
||||
pa_subscription *subscription;
|
||||
char *service_name;
|
||||
|
||||
AvahiEntryGroup *main_entry_group;
|
||||
|
||||
uint16_t port;
|
||||
|
||||
pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot;
|
||||
};
|
||||
|
||||
static void get_service_data(struct userdata *u, struct service *s, pa_sample_spec *ret_ss, char **ret_description) {
|
||||
assert(u && s && s->loaded.valid && ret_ss && ret_description);
|
||||
static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, const char **ret_description) {
|
||||
pa_assert(s);
|
||||
pa_assert(ret_ss);
|
||||
pa_assert(ret_description);
|
||||
|
||||
if (pa_sink_isinstance(s->device)) {
|
||||
pa_sink *sink = PA_SINK(s->device);
|
||||
|
||||
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_map = sink->channel_map;
|
||||
*ret_name = sink->name;
|
||||
*ret_description = sink->description;
|
||||
} else if (s->loaded.type == PA_NAMEREG_SOURCE) {
|
||||
pa_source *source = pa_idxset_get_by_index(u->core->sources, s->loaded.index);
|
||||
assert(source);
|
||||
|
||||
} else if (pa_source_isinstance(s->device)) {
|
||||
pa_source *source = PA_SOURCE(s->device);
|
||||
|
||||
*ret_ss = source->sample_spec;
|
||||
*ret_map = source->channel_map;
|
||||
*ret_name = source->name;
|
||||
*ret_description = source->description;
|
||||
} else
|
||||
assert(0);
|
||||
pa_assert_not_reached();
|
||||
}
|
||||
|
||||
static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) {
|
||||
char s[128];
|
||||
assert(c);
|
||||
|
||||
pa_assert(c);
|
||||
|
||||
l = avahi_string_list_add_pair(l, "server-version", PACKAGE_NAME" "PACKAGE_VERSION);
|
||||
l = avahi_string_list_add_pair(l, "user-name", pa_get_user_name(s, sizeof(s)));
|
||||
|
|
@ -130,325 +125,217 @@ static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) {
|
|||
return l;
|
||||
}
|
||||
|
||||
static int publish_service(struct userdata *u, struct service *s);
|
||||
static int publish_service(struct service *s);
|
||||
|
||||
static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
|
||||
struct service *s = userdata;
|
||||
|
||||
if (state == AVAHI_ENTRY_GROUP_COLLISION) {
|
||||
char *t;
|
||||
pa_assert(s);
|
||||
|
||||
t = avahi_alternative_service_name(s->service_name);
|
||||
pa_xfree(s->service_name);
|
||||
s->service_name = t;
|
||||
switch (state) {
|
||||
|
||||
publish_service(s->userdata, s);
|
||||
case AVAHI_ENTRY_GROUP_ESTABLISHED:
|
||||
pa_log_info("Successfully established service %s.", s->service_name);
|
||||
break;
|
||||
|
||||
case AVAHI_ENTRY_GROUP_COLLISION: {
|
||||
char *t;
|
||||
|
||||
t = avahi_alternative_service_name(s->service_name);
|
||||
pa_log_info("Name collision, renaming %s to %s.", s->service_name, t);
|
||||
pa_xfree(s->service_name);
|
||||
s->service_name = t;
|
||||
|
||||
publish_service(s);
|
||||
break;
|
||||
}
|
||||
|
||||
case AVAHI_ENTRY_GROUP_FAILURE: {
|
||||
pa_log("Failed to register service: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
|
||||
|
||||
avahi_entry_group_free(g);
|
||||
s->entry_group = NULL;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case AVAHI_ENTRY_GROUP_UNCOMMITED:
|
||||
case AVAHI_ENTRY_GROUP_REGISTERING:
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
static int publish_service(struct userdata *u, struct service *s) {
|
||||
static void service_free(struct service *s);
|
||||
|
||||
static int publish_service(struct service *s) {
|
||||
int r = -1;
|
||||
AvahiStringList *txt = NULL;
|
||||
const char *description = NULL, *name = NULL;
|
||||
pa_sample_spec ss;
|
||||
pa_channel_map map;
|
||||
char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
|
||||
|
||||
assert(u);
|
||||
assert(s);
|
||||
pa_assert(s);
|
||||
|
||||
if (!u->client || avahi_client_get_state(u->client) != AVAHI_CLIENT_S_RUNNING)
|
||||
if (!s->userdata->client || avahi_client_get_state(s->userdata->client) != AVAHI_CLIENT_S_RUNNING)
|
||||
return 0;
|
||||
|
||||
if ((s->published == PUBLISHED_REAL && s->loaded.valid) ||
|
||||
(s->published == PUBLISHED_AUTOLOAD && s->autoload.valid && !s->loaded.valid))
|
||||
return 0;
|
||||
|
||||
if (s->published != UNPUBLISHED) {
|
||||
if (!s->entry_group) {
|
||||
if (!(s->entry_group = avahi_entry_group_new(s->userdata->client, service_entry_group_callback, s))) {
|
||||
pa_log("avahi_entry_group_new(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
|
||||
goto finish;
|
||||
}
|
||||
} else
|
||||
avahi_entry_group_reset(s->entry_group);
|
||||
s->published = UNPUBLISHED;
|
||||
|
||||
txt = txt_record_server_data(s->userdata->core, txt);
|
||||
|
||||
get_service_data(s, &ss, &map, &name, &description);
|
||||
txt = avahi_string_list_add_pair(txt, "device", name);
|
||||
txt = avahi_string_list_add_printf(txt, "rate=%u", ss.rate);
|
||||
txt = avahi_string_list_add_printf(txt, "channels=%u", ss.channels);
|
||||
txt = avahi_string_list_add_pair(txt, "format", pa_sample_format_to_string(ss.format));
|
||||
txt = avahi_string_list_add_pair(txt, "channel_map", pa_channel_map_snprint(cm, sizeof(cm), &map));
|
||||
|
||||
if (avahi_entry_group_add_service_strlst(
|
||||
s->entry_group,
|
||||
AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
|
||||
0,
|
||||
s->service_name,
|
||||
pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
|
||||
NULL,
|
||||
NULL,
|
||||
s->userdata->port,
|
||||
txt) < 0) {
|
||||
|
||||
pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (s->loaded.valid || s->autoload.valid) {
|
||||
pa_namereg_type_t type;
|
||||
|
||||
if (!s->entry_group) {
|
||||
if (!(s->entry_group = avahi_entry_group_new(u->client, service_entry_group_callback, s))) {
|
||||
pa_log("avahi_entry_group_new(): %s", avahi_strerror(avahi_client_errno(u->client)));
|
||||
goto finish;
|
||||
}
|
||||
}
|
||||
|
||||
txt = avahi_string_list_add_pair(txt, "device", s->name);
|
||||
txt = txt_record_server_data(u->core, txt);
|
||||
|
||||
if (s->loaded.valid) {
|
||||
char *description;
|
||||
pa_sample_spec ss;
|
||||
|
||||
get_service_data(u, s, &ss, &description);
|
||||
|
||||
txt = avahi_string_list_add_printf(txt, "rate=%u", ss.rate);
|
||||
txt = avahi_string_list_add_printf(txt, "channels=%u", ss.channels);
|
||||
txt = avahi_string_list_add_pair(txt, "format", pa_sample_format_to_string(ss.format));
|
||||
if (description)
|
||||
txt = avahi_string_list_add_pair(txt, "description", description);
|
||||
|
||||
type = s->loaded.type;
|
||||
} else if (s->autoload.valid)
|
||||
type = s->autoload.type;
|
||||
|
||||
if (avahi_entry_group_add_service_strlst(
|
||||
s->entry_group,
|
||||
AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
|
||||
0,
|
||||
s->service_name,
|
||||
type == PA_NAMEREG_SINK ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE,
|
||||
NULL,
|
||||
NULL,
|
||||
u->port,
|
||||
txt) < 0) {
|
||||
|
||||
pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(u->client)));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (avahi_entry_group_commit(s->entry_group) < 0) {
|
||||
pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(u->client)));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
if (s->loaded.valid)
|
||||
s->published = PUBLISHED_REAL;
|
||||
else if (s->autoload.valid)
|
||||
s->published = PUBLISHED_AUTOLOAD;
|
||||
if (avahi_entry_group_commit(s->entry_group) < 0) {
|
||||
pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(s->userdata->client)));
|
||||
goto finish;
|
||||
}
|
||||
|
||||
r = 0;
|
||||
pa_log_debug("Successfully created entry group for %s.", s->service_name);
|
||||
|
||||
finish:
|
||||
|
||||
if (s->published == UNPUBLISHED) {
|
||||
/* Remove this service */
|
||||
/* Remove this service */
|
||||
if (r < 0)
|
||||
service_free(s);
|
||||
|
||||
if (s->entry_group)
|
||||
avahi_entry_group_free(s->entry_group);
|
||||
|
||||
pa_hashmap_remove(u->services, s->name);
|
||||
pa_xfree(s->name);
|
||||
pa_xfree(s->service_name);
|
||||
pa_xfree(s);
|
||||
}
|
||||
|
||||
if (txt)
|
||||
avahi_string_list_free(txt);
|
||||
avahi_string_list_free(txt);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static struct service *get_service(struct userdata *u, const char *name, const char *description) {
|
||||
static struct service *get_service(struct userdata *u, pa_object *device) {
|
||||
struct service *s;
|
||||
char hn[64];
|
||||
char hn[64], un[64];
|
||||
const char *n;
|
||||
|
||||
if ((s = pa_hashmap_get(u->services, name)))
|
||||
pa_assert(u);
|
||||
pa_object_assert_ref(device);
|
||||
|
||||
if ((s = pa_hashmap_get(u->services, device)))
|
||||
return s;
|
||||
|
||||
s = pa_xnew(struct service, 1);
|
||||
s->userdata = u;
|
||||
s->entry_group = NULL;
|
||||
s->published = UNPUBLISHED;
|
||||
s->name = pa_xstrdup(name);
|
||||
s->loaded.valid = s->autoload.valid = 0;
|
||||
s->service_name = pa_sprintf_malloc("%s on %s", description ? description : s->name, pa_get_host_name(hn, sizeof(hn)));
|
||||
s->device = device;
|
||||
|
||||
pa_hashmap_put(u->services, s->name, s);
|
||||
if (pa_sink_isinstance(device)) {
|
||||
if (!(n = PA_SINK(device)->description))
|
||||
n = PA_SINK(device)->name;
|
||||
} else {
|
||||
if (!(n = PA_SOURCE(device)->description))
|
||||
n = PA_SOURCE(device)->name;
|
||||
}
|
||||
|
||||
s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s",
|
||||
pa_get_user_name(un, sizeof(un)),
|
||||
pa_get_host_name(hn, sizeof(hn)),
|
||||
n),
|
||||
AVAHI_LABEL_MAX-1);
|
||||
|
||||
pa_hashmap_put(u->services, s->device, s);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static int publish_sink(struct userdata *u, pa_sink *s) {
|
||||
struct service *svc;
|
||||
int ret;
|
||||
assert(u && s);
|
||||
static void service_free(struct service *s) {
|
||||
pa_assert(s);
|
||||
|
||||
svc = get_service(u, s->name, s->description);
|
||||
if (svc->loaded.valid)
|
||||
return publish_service(u, svc);
|
||||
pa_hashmap_remove(s->userdata->services, s->device);
|
||||
|
||||
svc->loaded.valid = 1;
|
||||
svc->loaded.type = PA_NAMEREG_SINK;
|
||||
svc->loaded.index = s->index;
|
||||
|
||||
if ((ret = publish_service(u, svc)) < 0)
|
||||
return ret;
|
||||
|
||||
pa_dynarray_put(u->sink_dynarray, s->index, svc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int publish_source(struct userdata *u, pa_source *s) {
|
||||
struct service *svc;
|
||||
int ret;
|
||||
|
||||
assert(u && s);
|
||||
|
||||
svc = get_service(u, s->name, s->description);
|
||||
if (svc->loaded.valid)
|
||||
return publish_service(u, svc);
|
||||
|
||||
svc->loaded.valid = 1;
|
||||
svc->loaded.type = PA_NAMEREG_SOURCE;
|
||||
svc->loaded.index = s->index;
|
||||
|
||||
pa_dynarray_put(u->source_dynarray, s->index, svc);
|
||||
|
||||
if ((ret = publish_service(u, svc)) < 0)
|
||||
return ret;
|
||||
|
||||
pa_dynarray_put(u->sink_dynarray, s->index, svc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int publish_autoload(struct userdata *u, pa_autoload_entry *s) {
|
||||
struct service *svc;
|
||||
int ret;
|
||||
|
||||
assert(u && s);
|
||||
|
||||
svc = get_service(u, s->name, NULL);
|
||||
if (svc->autoload.valid)
|
||||
return publish_service(u, svc);
|
||||
|
||||
svc->autoload.valid = 1;
|
||||
svc->autoload.type = s->type;
|
||||
svc->autoload.index = s->index;
|
||||
|
||||
if ((ret = publish_service(u, svc)) < 0)
|
||||
return ret;
|
||||
|
||||
pa_dynarray_put(u->autoload_dynarray, s->index, svc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int remove_sink(struct userdata *u, uint32_t idx) {
|
||||
struct service *svc;
|
||||
assert(u && idx != PA_INVALID_INDEX);
|
||||
|
||||
if (!(svc = pa_dynarray_get(u->sink_dynarray, idx)))
|
||||
return 0;
|
||||
|
||||
if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SINK)
|
||||
return 0;
|
||||
|
||||
svc->loaded.valid = 0;
|
||||
pa_dynarray_put(u->sink_dynarray, idx, NULL);
|
||||
|
||||
return publish_service(u, svc);
|
||||
}
|
||||
|
||||
static int remove_source(struct userdata *u, uint32_t idx) {
|
||||
struct service *svc;
|
||||
assert(u && idx != PA_INVALID_INDEX);
|
||||
|
||||
if (!(svc = pa_dynarray_get(u->source_dynarray, idx)))
|
||||
return 0;
|
||||
|
||||
if (!svc->loaded.valid || svc->loaded.type != PA_NAMEREG_SOURCE)
|
||||
return 0;
|
||||
|
||||
svc->loaded.valid = 0;
|
||||
pa_dynarray_put(u->source_dynarray, idx, NULL);
|
||||
|
||||
return publish_service(u, svc);
|
||||
}
|
||||
|
||||
static int remove_autoload(struct userdata *u, uint32_t idx) {
|
||||
struct service *svc;
|
||||
assert(u && idx != PA_INVALID_INDEX);
|
||||
|
||||
if (!(svc = pa_dynarray_get(u->autoload_dynarray, idx)))
|
||||
return 0;
|
||||
|
||||
if (!svc->autoload.valid)
|
||||
return 0;
|
||||
|
||||
svc->autoload.valid = 0;
|
||||
pa_dynarray_put(u->autoload_dynarray, idx, NULL);
|
||||
|
||||
return publish_service(u, svc);
|
||||
}
|
||||
|
||||
static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, 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, idx))) {
|
||||
if (publish_sink(u, sink) < 0)
|
||||
goto fail;
|
||||
}
|
||||
} else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
|
||||
if (remove_sink(u, idx) < 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, idx))) {
|
||||
if (publish_source(u, source) < 0)
|
||||
goto fail;
|
||||
}
|
||||
} else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
|
||||
if (remove_source(u, idx) < 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, idx))) {
|
||||
if (publish_autoload(u, autoload) < 0)
|
||||
goto fail;
|
||||
}
|
||||
} else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
|
||||
if (remove_autoload(u, idx) < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
break;
|
||||
if (s->entry_group) {
|
||||
pa_log_debug("Removing entry group for %s.", s->service_name);
|
||||
avahi_entry_group_free(s->entry_group);
|
||||
}
|
||||
|
||||
return;
|
||||
pa_xfree(s->service_name);
|
||||
pa_xfree(s);
|
||||
}
|
||||
|
||||
fail:
|
||||
if (u->subscription) {
|
||||
pa_subscription_free(u->subscription);
|
||||
u->subscription = NULL;
|
||||
}
|
||||
static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) {
|
||||
pa_assert(c);
|
||||
pa_object_assert_ref(o);
|
||||
|
||||
publish_service(get_service(u, o));
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) {
|
||||
struct service *s;
|
||||
|
||||
pa_assert(c);
|
||||
pa_object_assert_ref(o);
|
||||
|
||||
if ((s = pa_hashmap_get(u->services, o)))
|
||||
service_free(s);
|
||||
|
||||
return PA_HOOK_OK;
|
||||
}
|
||||
|
||||
static int publish_main_service(struct userdata *u);
|
||||
|
||||
static void main_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) {
|
||||
struct userdata *u = userdata;
|
||||
assert(u);
|
||||
pa_assert(u);
|
||||
|
||||
if (state == AVAHI_ENTRY_GROUP_COLLISION) {
|
||||
char *t;
|
||||
switch (state) {
|
||||
|
||||
t = avahi_alternative_service_name(u->service_name);
|
||||
pa_xfree(u->service_name);
|
||||
u->service_name = t;
|
||||
case AVAHI_ENTRY_GROUP_ESTABLISHED:
|
||||
pa_log_info("Successfully established main service.");
|
||||
break;
|
||||
|
||||
publish_main_service(u);
|
||||
case AVAHI_ENTRY_GROUP_COLLISION: {
|
||||
char *t;
|
||||
|
||||
t = avahi_alternative_service_name(u->service_name);
|
||||
pa_log_info("Name collision: renaming main service %s to %s.", u->service_name, t);
|
||||
pa_xfree(u->service_name);
|
||||
u->service_name = t;
|
||||
|
||||
publish_main_service(u);
|
||||
break;
|
||||
}
|
||||
|
||||
case AVAHI_ENTRY_GROUP_FAILURE: {
|
||||
pa_log("Failed to register main service: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
|
||||
|
||||
avahi_entry_group_free(g);
|
||||
u->main_entry_group = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
case AVAHI_ENTRY_GROUP_UNCOMMITED:
|
||||
case AVAHI_ENTRY_GROUP_REGISTERING:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -456,6 +343,8 @@ static int publish_main_service(struct userdata *u) {
|
|||
AvahiStringList *txt = NULL;
|
||||
int r = -1;
|
||||
|
||||
pa_assert(u);
|
||||
|
||||
if (!u->main_entry_group) {
|
||||
if (!(u->main_entry_group = avahi_entry_group_new(u->client, main_entry_group_callback, u))) {
|
||||
pa_log("avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(u->client)));
|
||||
|
|
@ -464,7 +353,7 @@ static int publish_main_service(struct userdata *u) {
|
|||
} else
|
||||
avahi_entry_group_reset(u->main_entry_group);
|
||||
|
||||
txt = txt_record_server_data(u->core, NULL);
|
||||
txt = txt_record_server_data(u->core, txt);
|
||||
|
||||
if (avahi_entry_group_add_service_strlst(
|
||||
u->main_entry_group,
|
||||
|
|
@ -497,26 +386,18 @@ fail:
|
|||
static int publish_all_services(struct userdata *u) {
|
||||
pa_sink *sink;
|
||||
pa_source *source;
|
||||
pa_autoload_entry *autoload;
|
||||
int r = -1;
|
||||
uint32_t idx;
|
||||
|
||||
assert(u);
|
||||
pa_assert(u);
|
||||
|
||||
pa_log_debug("Publishing services in Zeroconf");
|
||||
|
||||
for (sink = pa_idxset_first(u->core->sinks, &idx); sink; sink = pa_idxset_next(u->core->sinks, &idx))
|
||||
if (publish_sink(u, sink) < 0)
|
||||
goto fail;
|
||||
for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx)))
|
||||
publish_service(get_service(u, PA_OBJECT(sink)));
|
||||
|
||||
for (source = pa_idxset_first(u->core->sources, &idx); source; source = pa_idxset_next(u->core->sources, &idx))
|
||||
if (publish_source(u, source) < 0)
|
||||
goto fail;
|
||||
|
||||
if (u->core->autoload_idxset)
|
||||
for (autoload = pa_idxset_first(u->core->autoload_idxset, &idx); autoload; autoload = pa_idxset_next(u->core->autoload_idxset, &idx))
|
||||
if (publish_autoload(u, autoload) < 0)
|
||||
goto fail;
|
||||
for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx)))
|
||||
publish_service(get_service(u, PA_OBJECT(source)));
|
||||
|
||||
if (publish_main_service(u) < 0)
|
||||
goto fail;
|
||||
|
|
@ -527,38 +408,44 @@ fail:
|
|||
return r;
|
||||
}
|
||||
|
||||
static void unpublish_all_services(struct userdata *u, int rem) {
|
||||
static void unpublish_all_services(struct userdata *u, pa_bool_t rem) {
|
||||
void *state = NULL;
|
||||
struct service *s;
|
||||
|
||||
assert(u);
|
||||
pa_assert(u);
|
||||
|
||||
pa_log_debug("Unpublishing services in Zeroconf");
|
||||
|
||||
while ((s = pa_hashmap_iterate(u->services, &state, NULL))) {
|
||||
if (s->entry_group) {
|
||||
if (rem) {
|
||||
pa_log_debug("Removing entry group for %s.", s->service_name);
|
||||
avahi_entry_group_free(s->entry_group);
|
||||
s->entry_group = NULL;
|
||||
} else
|
||||
} else {
|
||||
avahi_entry_group_reset(s->entry_group);
|
||||
pa_log_debug("Resetting entry group for %s.", s->service_name);
|
||||
}
|
||||
}
|
||||
|
||||
s->published = UNPUBLISHED;
|
||||
}
|
||||
|
||||
if (u->main_entry_group) {
|
||||
if (rem) {
|
||||
pa_log_debug("Removing main entry group.");
|
||||
avahi_entry_group_free(u->main_entry_group);
|
||||
u->main_entry_group = NULL;
|
||||
} else
|
||||
} else {
|
||||
avahi_entry_group_reset(u->main_entry_group);
|
||||
pa_log_debug("Resetting main entry group.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) {
|
||||
struct userdata *u = userdata;
|
||||
assert(c);
|
||||
|
||||
pa_assert(c);
|
||||
pa_assert(u);
|
||||
|
||||
u->client = c;
|
||||
|
||||
|
|
@ -568,13 +455,17 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda
|
|||
break;
|
||||
|
||||
case AVAHI_CLIENT_S_COLLISION:
|
||||
unpublish_all_services(u, 0);
|
||||
pa_log_debug("Host name collision");
|
||||
unpublish_all_services(u, FALSE);
|
||||
break;
|
||||
|
||||
case AVAHI_CLIENT_FAILURE:
|
||||
if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
|
||||
int error;
|
||||
unpublish_all_services(u, 1);
|
||||
|
||||
pa_log_debug("Avahi daemon disconnected.");
|
||||
|
||||
unpublish_all_services(u, TRUE);
|
||||
avahi_client_free(u->client);
|
||||
|
||||
if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error)))
|
||||
|
|
@ -587,11 +478,12 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda
|
|||
}
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
|
||||
struct userdata *u;
|
||||
uint32_t port = PA_NATIVE_DEFAULT_PORT;
|
||||
pa_modargs *ma = NULL;
|
||||
char hn[256];
|
||||
char hn[256], un[256];
|
||||
int error;
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
|
|
@ -599,30 +491,29 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port == 0 || port >= 0xFFFF) {
|
||||
if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port <= 0 || port > 0xFFFF) {
|
||||
pa_log("invalid port specified.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
m->userdata = u = pa_xnew(struct userdata, 1);
|
||||
u->core = c;
|
||||
u->core = m->core;
|
||||
u->port = (uint16_t) port;
|
||||
|
||||
u->avahi_poll = pa_avahi_poll_new(c->mainloop);
|
||||
u->avahi_poll = pa_avahi_poll_new(m->core->mainloop);
|
||||
|
||||
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->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
|
||||
|
||||
u->subscription = pa_subscription_new(c,
|
||||
PA_SUBSCRIPTION_MASK_SINK|
|
||||
PA_SUBSCRIPTION_MASK_SOURCE|
|
||||
PA_SUBSCRIPTION_MASK_AUTOLOAD, subscribe_callback, u);
|
||||
u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) device_new_or_changed_cb, u);
|
||||
u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_DESCRIPTION_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u);
|
||||
u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) device_unlink_cb, u);
|
||||
u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], (pa_hook_cb_t) device_new_or_changed_cb, u);
|
||||
u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_DESCRIPTION_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u);
|
||||
u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], (pa_hook_cb_t) device_unlink_cb, u);
|
||||
|
||||
u->main_entry_group = NULL;
|
||||
|
||||
u->service_name = pa_xstrdup(pa_get_host_name(hn, sizeof(hn)));
|
||||
u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", pa_get_user_name(un, sizeof(un)), pa_get_host_name(hn, sizeof(hn))), AVAHI_LABEL_MAX);
|
||||
|
||||
if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) {
|
||||
pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error));
|
||||
|
|
@ -634,7 +525,7 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
return 0;
|
||||
|
||||
fail:
|
||||
pa__done(c, m);
|
||||
pa__done(m);
|
||||
|
||||
if (ma)
|
||||
pa_modargs_free(ma);
|
||||
|
|
@ -642,41 +533,34 @@ fail:
|
|||
return -1;
|
||||
}
|
||||
|
||||
static void service_free(void *p, void *userdata) {
|
||||
struct service *s = p;
|
||||
struct userdata *u = userdata;
|
||||
|
||||
assert(s);
|
||||
assert(u);
|
||||
|
||||
if (s->entry_group)
|
||||
avahi_entry_group_free(s->entry_group);
|
||||
|
||||
pa_xfree(s->service_name);
|
||||
pa_xfree(s->name);
|
||||
pa_xfree(s);
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata*u;
|
||||
assert(c && m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
||||
if (u->services)
|
||||
pa_hashmap_free(u->services, service_free, u);
|
||||
if (u->services) {
|
||||
struct service *s;
|
||||
|
||||
if (u->subscription)
|
||||
pa_subscription_free(u->subscription);
|
||||
while ((s = pa_hashmap_get_first(u->services)))
|
||||
service_free(s);
|
||||
|
||||
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);
|
||||
pa_hashmap_free(u->services, NULL, NULL);
|
||||
}
|
||||
|
||||
if (u->sink_new_slot)
|
||||
pa_hook_slot_free(u->sink_new_slot);
|
||||
if (u->source_new_slot)
|
||||
pa_hook_slot_free(u->source_new_slot);
|
||||
if (u->sink_changed_slot)
|
||||
pa_hook_slot_free(u->sink_changed_slot);
|
||||
if (u->source_changed_slot)
|
||||
pa_hook_slot_free(u->source_changed_slot);
|
||||
if (u->sink_unlink_slot)
|
||||
pa_hook_slot_free(u->sink_unlink_slot);
|
||||
if (u->source_unlink_slot)
|
||||
pa_hook_slot_free(u->source_unlink_slot);
|
||||
|
||||
if (u->main_entry_group)
|
||||
avahi_entry_group_free(u->main_entry_group);
|
||||
|
|
@ -690,4 +574,3 @@ void pa__done(pa_core *c, pa_module*m) {
|
|||
pa_xfree(u->service_name);
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@
|
|||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <sys/soundcard.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -37,9 +36,11 @@
|
|||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/macro.h>
|
||||
|
||||
#include "oss-util.h"
|
||||
|
||||
|
|
@ -47,46 +48,43 @@ int pa_oss_open(const char *device, int *mode, int* pcaps) {
|
|||
int fd = -1;
|
||||
int caps;
|
||||
|
||||
assert(device && mode && (*mode == O_RDWR || *mode == O_RDONLY || *mode == O_WRONLY));
|
||||
pa_assert(device);
|
||||
pa_assert(mode);
|
||||
pa_assert(*mode == O_RDWR || *mode == O_RDONLY || *mode == O_WRONLY);
|
||||
|
||||
if(!pcaps)
|
||||
pcaps = ∩︀
|
||||
|
||||
if (*mode == O_RDWR) {
|
||||
if ((fd = open(device, O_RDWR|O_NDELAY)) >= 0) {
|
||||
int dcaps, *tcaps;
|
||||
if ((fd = open(device, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) {
|
||||
ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0);
|
||||
|
||||
tcaps = pcaps ? pcaps : &dcaps;
|
||||
|
||||
if (ioctl(fd, SNDCTL_DSP_GETCAPS, tcaps) < 0) {
|
||||
if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) {
|
||||
pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (*tcaps & DSP_CAP_DUPLEX)
|
||||
if (*pcaps & DSP_CAP_DUPLEX)
|
||||
goto success;
|
||||
|
||||
pa_log_warn("'%s' doesn't support full duplex", device);
|
||||
|
||||
close(fd);
|
||||
pa_close(fd);
|
||||
}
|
||||
|
||||
if ((fd = open(device, (*mode = O_WRONLY)|O_NDELAY)) < 0) {
|
||||
if ((fd = open(device, (*mode = O_RDONLY)|O_NDELAY)) < 0) {
|
||||
if ((fd = open(device, (*mode = O_WRONLY)|O_NDELAY|O_NOCTTY)) < 0) {
|
||||
if ((fd = open(device, (*mode = O_RDONLY)|O_NDELAY|O_NOCTTY)) < 0) {
|
||||
pa_log("open('%s'): %s", device, pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ((fd = open(device, *mode|O_NDELAY)) < 0) {
|
||||
if ((fd = open(device, *mode|O_NDELAY|O_NOCTTY)) < 0) {
|
||||
pa_log("open('%s'): %s", device, pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
success:
|
||||
|
||||
*pcaps = 0;
|
||||
|
||||
if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) {
|
||||
|
|
@ -94,12 +92,14 @@ success:
|
|||
goto fail;
|
||||
}
|
||||
|
||||
success:
|
||||
|
||||
pa_log_debug("capabilities:%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
|
||||
*pcaps & DSP_CAP_BATCH ? " BATCH" : "",
|
||||
#ifdef DSP_CAP_BIND
|
||||
*pcaps & DSP_CAP_BIND ? " BIND" : "",
|
||||
#else
|
||||
"",
|
||||
"",
|
||||
#endif
|
||||
*pcaps & DSP_CAP_COPROC ? " COPROC" : "",
|
||||
*pcaps & DSP_CAP_DUPLEX ? " DUPLEX" : "",
|
||||
|
|
@ -122,7 +122,7 @@ success:
|
|||
#ifdef DSP_CAP_MULTI
|
||||
*pcaps & DSP_CAP_MULTI ? " MULTI" : "",
|
||||
#else
|
||||
"",
|
||||
"",
|
||||
#endif
|
||||
#ifdef DSP_CAP_OUTPUT
|
||||
*pcaps & DSP_CAP_OUTPUT ? " OUTPUT" : "",
|
||||
|
|
@ -142,13 +142,13 @@ success:
|
|||
#endif
|
||||
*pcaps & DSP_CAP_TRIGGER ? " TRIGGER" : "");
|
||||
|
||||
pa_fd_set_cloexec(fd, 1);
|
||||
pa_make_fd_cloexec(fd);
|
||||
|
||||
return fd;
|
||||
|
||||
fail:
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
pa_close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -166,7 +166,8 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss) {
|
|||
[PA_SAMPLE_FLOAT32BE] = AFMT_QUERY, /* not supported */
|
||||
};
|
||||
|
||||
assert(fd >= 0 && ss);
|
||||
pa_assert(fd >= 0);
|
||||
pa_assert(ss);
|
||||
|
||||
orig_format = ss->format;
|
||||
|
||||
|
|
@ -199,7 +200,7 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss) {
|
|||
pa_log("SNDCTL_DSP_CHANNELS: %s", pa_cstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
assert(channels > 0);
|
||||
pa_assert(channels > 0);
|
||||
|
||||
if (ss->channels != channels) {
|
||||
pa_log_warn("device doesn't support %i channels, using %i channels.", ss->channels, channels);
|
||||
|
|
@ -211,7 +212,7 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss) {
|
|||
pa_log("SNDCTL_DSP_SPEED: %s", pa_cstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
assert(speed > 0);
|
||||
pa_assert(speed > 0);
|
||||
|
||||
if (ss->rate != (unsigned) speed) {
|
||||
pa_log_warn("device doesn't support %i Hz, changed to %i Hz.", ss->rate, speed);
|
||||
|
|
@ -248,27 +249,29 @@ int pa_oss_set_fragments(int fd, int nfrags, int frag_size) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume) {
|
||||
int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume) {
|
||||
char cv[PA_CVOLUME_SNPRINT_MAX];
|
||||
unsigned vol;
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(ss);
|
||||
assert(volume);
|
||||
pa_assert(fd >= 0);
|
||||
pa_assert(ss);
|
||||
pa_assert(volume);
|
||||
|
||||
if (ioctl(fd, mixer, &vol) < 0)
|
||||
return -1;
|
||||
|
||||
pa_cvolume_reset(volume, ss->channels);
|
||||
|
||||
volume->values[0] = ((vol & 0xFF) * PA_VOLUME_NORM) / 100;
|
||||
|
||||
if ((volume->channels = ss->channels) >= 2)
|
||||
if (volume->channels >= 2)
|
||||
volume->values[1] = (((vol >> 8) & 0xFF) * PA_VOLUME_NORM) / 100;
|
||||
|
||||
pa_log_debug("Read mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pa_oss_set_volume(int fd, int mixer, const pa_sample_spec *ss, const pa_cvolume *volume) {
|
||||
int pa_oss_set_volume(int fd, long mixer, const pa_sample_spec *ss, const pa_cvolume *volume) {
|
||||
char cv[PA_CVOLUME_SNPRINT_MAX];
|
||||
unsigned vol;
|
||||
pa_volume_t l, r;
|
||||
|
|
@ -289,40 +292,38 @@ static int pa_oss_set_volume(int fd, int mixer, const pa_sample_spec *ss, const
|
|||
return 0;
|
||||
}
|
||||
|
||||
int pa_oss_get_pcm_volume(int fd, const pa_sample_spec *ss, pa_cvolume *volume) {
|
||||
return pa_oss_get_volume(fd, SOUND_MIXER_READ_PCM, ss, volume);
|
||||
}
|
||||
static int get_device_number(const char *dev) {
|
||||
char buf[PATH_MAX];
|
||||
const char *p, *e;
|
||||
|
||||
int pa_oss_set_pcm_volume(int fd, const pa_sample_spec *ss, const pa_cvolume *volume) {
|
||||
return pa_oss_set_volume(fd, SOUND_MIXER_WRITE_PCM, ss, volume);
|
||||
}
|
||||
if (readlink(dev, buf, sizeof(buf)) < 0) {
|
||||
if (errno != EINVAL && errno != ENOLINK)
|
||||
return -1;
|
||||
|
||||
int pa_oss_get_input_volume(int fd, const pa_sample_spec *ss, pa_cvolume *volume) {
|
||||
return pa_oss_get_volume(fd, SOUND_MIXER_READ_IGAIN, ss, volume);
|
||||
}
|
||||
p = dev;
|
||||
} else
|
||||
p = buf;
|
||||
|
||||
int pa_oss_set_input_volume(int fd, const pa_sample_spec *ss, const pa_cvolume *volume) {
|
||||
return pa_oss_set_volume(fd, SOUND_MIXER_WRITE_IGAIN, ss, volume);
|
||||
if ((e = strrchr(p, '/')))
|
||||
p = e+1;
|
||||
|
||||
if (p == 0)
|
||||
return 0;
|
||||
|
||||
p = strchr(p, 0) -1;
|
||||
|
||||
if (*p >= '0' && *p <= '9')
|
||||
return *p - '0';
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int pa_oss_get_hw_description(const char *dev, char *name, size_t l) {
|
||||
FILE *f;
|
||||
const char *e = NULL;
|
||||
int n, r = -1;
|
||||
int b = 0;
|
||||
|
||||
if (strncmp(dev, "/dev/dsp", 8) == 0)
|
||||
e = dev+8;
|
||||
else if (strncmp(dev, "/dev/adsp", 9) == 0)
|
||||
e = dev+9;
|
||||
else
|
||||
return -1;
|
||||
|
||||
if (*e == 0)
|
||||
n = 0;
|
||||
else if (*e >= '0' && *e <= '9' && *(e+1) == 0)
|
||||
n = *e - '0';
|
||||
else
|
||||
if ((n = get_device_number(dev)) < 0)
|
||||
return -1;
|
||||
|
||||
if (!(f = fopen("/dev/sndstat", "r")) &&
|
||||
|
|
@ -357,7 +358,7 @@ int pa_oss_get_hw_description(const char *dev, char *name, size_t l) {
|
|||
|
||||
if (device == n) {
|
||||
char *k = strchr(line, ':');
|
||||
assert(k);
|
||||
pa_assert(k);
|
||||
k++;
|
||||
k += strspn(k, " ");
|
||||
|
||||
|
|
@ -373,3 +374,34 @@ int pa_oss_get_hw_description(const char *dev, char *name, size_t l) {
|
|||
fclose(f);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int open_mixer(const char *mixer) {
|
||||
int fd;
|
||||
|
||||
if ((fd = open(mixer, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0)
|
||||
return fd;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int pa_oss_open_mixer_for_device(const char *device) {
|
||||
int n;
|
||||
char *fn;
|
||||
int fd;
|
||||
|
||||
if ((n = get_device_number(device)) < 0)
|
||||
return -1;
|
||||
|
||||
if (n == 0)
|
||||
if ((fd = open_mixer("/dev/mixer")) >= 0)
|
||||
return fd;
|
||||
|
||||
fn = pa_sprintf_malloc("/dev/mixer%i", n);
|
||||
fd = open_mixer(fn);
|
||||
pa_xfree(fn);
|
||||
|
||||
if (fd < 0)
|
||||
pa_log_warn("Failed to open mixer '%s': %s", device, pa_cstrerror(errno));
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,12 +33,11 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss);
|
|||
|
||||
int pa_oss_set_fragments(int fd, int frags, int frag_size);
|
||||
|
||||
int pa_oss_get_pcm_volume(int fd, const pa_sample_spec *ss, pa_cvolume *volume);
|
||||
int pa_oss_set_pcm_volume(int fd, const pa_sample_spec *ss, const pa_cvolume *volume);
|
||||
|
||||
int pa_oss_get_input_volume(int fd, const pa_sample_spec *ss, pa_cvolume *volume);
|
||||
int pa_oss_set_input_volume(int fd, const pa_sample_spec *ss, const pa_cvolume *volume);
|
||||
int pa_oss_set_volume(int fd, long mixer, const pa_sample_spec *ss, const pa_cvolume *volume);
|
||||
int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume);
|
||||
|
||||
int pa_oss_get_hw_description(const char *dev, char *name, size_t l);
|
||||
|
||||
int pa_oss_open_mixer_for_device(const char *device);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@
|
|||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
|
|
@ -32,6 +31,7 @@
|
|||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <pulse/timeval.h>
|
||||
#include <pulse/xmalloc.h>
|
||||
|
|
@ -47,6 +47,10 @@
|
|||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/namereg.h>
|
||||
#include <pulsecore/sample-util.h>
|
||||
#include <pulsecore/macro.h>
|
||||
#include <pulsecore/atomic.h>
|
||||
#include <pulsecore/rtclock.h>
|
||||
#include <pulsecore/atomic.h>
|
||||
|
||||
#include "module-rtp-recv-symdef.h"
|
||||
|
||||
|
|
@ -66,7 +70,7 @@ PA_MODULE_USAGE(
|
|||
#define DEFAULT_SAP_ADDRESS "224.0.0.56"
|
||||
#define MEMBLOCKQ_MAXLENGTH (1024*170)
|
||||
#define MAX_SESSIONS 16
|
||||
#define DEATH_TIMEOUT 20000000
|
||||
#define DEATH_TIMEOUT 20
|
||||
|
||||
static const char* const valid_modargs[] = {
|
||||
"sink",
|
||||
|
|
@ -76,102 +80,126 @@ static const char* const valid_modargs[] = {
|
|||
|
||||
struct session {
|
||||
struct userdata *userdata;
|
||||
PA_LLIST_FIELDS(struct session);
|
||||
|
||||
pa_sink_input *sink_input;
|
||||
pa_memblockq *memblockq;
|
||||
|
||||
pa_time_event *death_event;
|
||||
|
||||
int first_packet;
|
||||
pa_bool_t first_packet;
|
||||
uint32_t ssrc;
|
||||
uint32_t offset;
|
||||
|
||||
struct pa_sdp_info sdp_info;
|
||||
|
||||
pa_rtp_context rtp_context;
|
||||
pa_io_event* rtp_event;
|
||||
|
||||
pa_rtpoll_item *rtpoll_item;
|
||||
|
||||
pa_atomic_t timestamp;
|
||||
};
|
||||
|
||||
struct userdata {
|
||||
pa_module *module;
|
||||
pa_core *core;
|
||||
|
||||
pa_sap_context sap_context;
|
||||
pa_io_event* sap_event;
|
||||
|
||||
pa_hashmap *by_origin;
|
||||
pa_time_event *check_death_event;
|
||||
|
||||
char *sink_name;
|
||||
|
||||
PA_LLIST_HEAD(struct session, sessions);
|
||||
pa_hashmap *by_origin;
|
||||
int n_sessions;
|
||||
};
|
||||
|
||||
static void session_free(struct session *s, int from_hash);
|
||||
static void session_free(struct session *s);
|
||||
|
||||
static int sink_input_peek(pa_sink_input *i, pa_memchunk *chunk) {
|
||||
/* Called from I/O thread context */
|
||||
static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||
struct session *s = PA_SINK_INPUT(o)->userdata;
|
||||
|
||||
switch (code) {
|
||||
case PA_SINK_INPUT_MESSAGE_GET_LATENCY:
|
||||
*((pa_usec_t*) data) = pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &s->sink_input->sample_spec);
|
||||
|
||||
/* Fall through, the default handler will add in the extra
|
||||
* latency added by the resampler */
|
||||
break;
|
||||
}
|
||||
|
||||
return pa_sink_input_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static int sink_input_peek(pa_sink_input *i, size_t length, pa_memchunk *chunk) {
|
||||
struct session *s;
|
||||
assert(i);
|
||||
s = i->userdata;
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(s = i->userdata);
|
||||
|
||||
return pa_memblockq_peek(s->memblockq, chunk);
|
||||
}
|
||||
|
||||
static void sink_input_drop(pa_sink_input *i, const pa_memchunk *chunk, size_t length) {
|
||||
/* Called from I/O thread context */
|
||||
static void sink_input_drop(pa_sink_input *i, size_t length) {
|
||||
struct session *s;
|
||||
assert(i);
|
||||
s = i->userdata;
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(s = i->userdata);
|
||||
|
||||
pa_memblockq_drop(s->memblockq, chunk, length);
|
||||
pa_memblockq_drop(s->memblockq, length);
|
||||
}
|
||||
|
||||
/* Called from main context */
|
||||
static void sink_input_kill(pa_sink_input* i) {
|
||||
struct session *s;
|
||||
assert(i);
|
||||
s = i->userdata;
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(s = i->userdata);
|
||||
|
||||
session_free(s, 1);
|
||||
session_free(s);
|
||||
}
|
||||
|
||||
static pa_usec_t sink_input_get_latency(pa_sink_input *i) {
|
||||
struct session *s;
|
||||
assert(i);
|
||||
s = i->userdata;
|
||||
|
||||
return pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &i->sample_spec);
|
||||
}
|
||||
|
||||
static void rtp_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) {
|
||||
struct session *s = userdata;
|
||||
/* Called from I/O thread context */
|
||||
static int rtpoll_work_cb(pa_rtpoll_item *i) {
|
||||
pa_memchunk chunk;
|
||||
int64_t k, j, delta;
|
||||
struct timeval tv;
|
||||
struct timeval now;
|
||||
struct session *s;
|
||||
struct pollfd *p;
|
||||
|
||||
assert(m);
|
||||
assert(e);
|
||||
assert(s);
|
||||
assert(fd == s->rtp_context.fd);
|
||||
assert(flags == PA_IO_EVENT_INPUT);
|
||||
pa_assert_se(s = pa_rtpoll_item_get_userdata(i));
|
||||
|
||||
if (pa_rtp_recv(&s->rtp_context, &chunk, s->userdata->core->mempool) < 0)
|
||||
return;
|
||||
p = pa_rtpoll_item_get_pollfd(i, NULL);
|
||||
|
||||
if (p->revents & (POLLERR|POLLNVAL|POLLHUP|POLLOUT)) {
|
||||
pa_log("poll() signalled bad revents.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((p->revents & POLLIN) == 0)
|
||||
return 0;
|
||||
|
||||
p->revents = 0;
|
||||
|
||||
if (pa_rtp_recv(&s->rtp_context, &chunk, s->userdata->module->core->mempool) < 0)
|
||||
return 0;
|
||||
|
||||
if (s->sdp_info.payload != s->rtp_context.payload) {
|
||||
pa_memblock_unref(chunk.memblock);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!s->first_packet) {
|
||||
s->first_packet = 1;
|
||||
s->first_packet = TRUE;
|
||||
|
||||
s->ssrc = s->rtp_context.ssrc;
|
||||
s->offset = s->rtp_context.timestamp;
|
||||
|
||||
if (s->ssrc == s->userdata->core->cookie)
|
||||
pa_log_warn("WARNING! Detected RTP packet loop!");
|
||||
if (s->ssrc == s->userdata->module->core->cookie)
|
||||
pa_log_warn("Detected RTP packet loop!");
|
||||
} else {
|
||||
if (s->ssrc != s->rtp_context.ssrc) {
|
||||
pa_memblock_unref(chunk.memblock);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -197,26 +225,49 @@ static void rtp_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event
|
|||
|
||||
pa_memblock_unref(chunk.memblock);
|
||||
|
||||
/* Reset death timer */
|
||||
pa_gettimeofday(&tv);
|
||||
pa_timeval_add(&tv, DEATH_TIMEOUT);
|
||||
m->time_restart(s->death_event, &tv);
|
||||
pa_rtclock_get(&now);
|
||||
pa_atomic_store(&s->timestamp, now.tv_sec);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void death_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *tv, void *userdata) {
|
||||
struct session *s = userdata;
|
||||
/* Called from I/O thread context */
|
||||
static void sink_input_attach(pa_sink_input *i) {
|
||||
struct session *s;
|
||||
struct pollfd *p;
|
||||
|
||||
assert(m);
|
||||
assert(t);
|
||||
assert(tv);
|
||||
assert(s);
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(s = i->userdata);
|
||||
|
||||
session_free(s, 1);
|
||||
pa_assert(!s->rtpoll_item);
|
||||
s->rtpoll_item = pa_rtpoll_item_new(i->sink->rtpoll, PA_RTPOLL_LATE, 1);
|
||||
|
||||
p = pa_rtpoll_item_get_pollfd(s->rtpoll_item, NULL);
|
||||
p->fd = s->rtp_context.fd;
|
||||
p->events = POLLIN;
|
||||
p->revents = 0;
|
||||
|
||||
pa_rtpoll_item_set_work_callback(s->rtpoll_item, rtpoll_work_cb);
|
||||
pa_rtpoll_item_set_userdata(s->rtpoll_item, s);
|
||||
}
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static void sink_input_detach(pa_sink_input *i) {
|
||||
struct session *s;
|
||||
pa_sink_input_assert_ref(i);
|
||||
pa_assert_se(s = i->userdata);
|
||||
|
||||
pa_assert(s->rtpoll_item);
|
||||
pa_rtpoll_item_free(s->rtpoll_item);
|
||||
s->rtpoll_item = NULL;
|
||||
}
|
||||
|
||||
static int mcast_socket(const struct sockaddr* sa, socklen_t salen) {
|
||||
int af, fd = -1, r, one;
|
||||
|
||||
pa_assert(sa);
|
||||
pa_assert(salen > 0);
|
||||
|
||||
af = sa->sa_family;
|
||||
if ((fd = socket(af, SOCK_DGRAM, 0)) < 0) {
|
||||
pa_log("Failed to create socket: %s", pa_cstrerror(errno));
|
||||
|
|
@ -262,27 +313,34 @@ fail:
|
|||
|
||||
static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_info) {
|
||||
struct session *s = NULL;
|
||||
struct timeval tv;
|
||||
char *c;
|
||||
pa_sink *sink;
|
||||
int fd = -1;
|
||||
pa_memblock *silence;
|
||||
pa_sink_input_new_data data;
|
||||
struct timeval now;
|
||||
|
||||
pa_assert(u);
|
||||
pa_assert(sdp_info);
|
||||
|
||||
if (u->n_sessions >= MAX_SESSIONS) {
|
||||
pa_log("session limit reached.");
|
||||
pa_log("Session limit reached.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!(sink = pa_namereg_get(u->core, u->sink_name, PA_NAMEREG_SINK, 1))) {
|
||||
pa_log("sink does not exist.");
|
||||
if (!(sink = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1))) {
|
||||
pa_log("Sink does not exist.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
s = pa_xnew0(struct session, 1);
|
||||
s->userdata = u;
|
||||
s->first_packet = 0;
|
||||
s->first_packet = FALSE;
|
||||
s->sdp_info = *sdp_info;
|
||||
s->rtpoll_item = NULL;
|
||||
|
||||
pa_rtclock_get(&now);
|
||||
pa_atomic_store(&s->timestamp, now.tv_sec);
|
||||
|
||||
if ((fd = mcast_socket((const struct sockaddr*) &sdp_info->sa, sdp_info->salen)) < 0)
|
||||
goto fail;
|
||||
|
|
@ -299,25 +357,27 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in
|
|||
data.module = u->module;
|
||||
pa_sink_input_new_data_set_sample_spec(&data, &sdp_info->sample_spec);
|
||||
|
||||
s->sink_input = pa_sink_input_new(u->core, &data, 0);
|
||||
s->sink_input = pa_sink_input_new(u->module->core, &data, 0);
|
||||
pa_xfree(c);
|
||||
|
||||
if (!s->sink_input) {
|
||||
pa_log("failed to create sink input.");
|
||||
pa_log("Failed to create sink input.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
s->sink_input->userdata = s;
|
||||
|
||||
s->sink_input->parent.process_msg = sink_input_process_msg;
|
||||
s->sink_input->peek = sink_input_peek;
|
||||
s->sink_input->drop = sink_input_drop;
|
||||
s->sink_input->kill = sink_input_kill;
|
||||
s->sink_input->get_latency = sink_input_get_latency;
|
||||
s->sink_input->attach = sink_input_attach;
|
||||
s->sink_input->detach = sink_input_detach;
|
||||
|
||||
silence = pa_silence_memblock_new(s->userdata->core->mempool,
|
||||
&s->sink_input->sample_spec,
|
||||
(pa_bytes_per_second(&s->sink_input->sample_spec)/128/pa_frame_size(&s->sink_input->sample_spec))*
|
||||
pa_frame_size(&s->sink_input->sample_spec));
|
||||
silence = pa_silence_memblock_new(
|
||||
s->userdata->module->core->mempool,
|
||||
&s->sink_input->sample_spec,
|
||||
pa_frame_align(pa_bytes_per_second(&s->sink_input->sample_spec)/128, &s->sink_input->sample_spec));
|
||||
|
||||
s->memblockq = pa_memblockq_new(
|
||||
0,
|
||||
|
|
@ -330,54 +390,44 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in
|
|||
|
||||
pa_memblock_unref(silence);
|
||||
|
||||
s->rtp_event = u->core->mainloop->io_new(u->core->mainloop, fd, PA_IO_EVENT_INPUT, rtp_event_cb, s);
|
||||
|
||||
pa_gettimeofday(&tv);
|
||||
pa_timeval_add(&tv, DEATH_TIMEOUT);
|
||||
s->death_event = u->core->mainloop->time_new(u->core->mainloop, &tv, death_event_cb, s);
|
||||
|
||||
pa_hashmap_put(s->userdata->by_origin, s->sdp_info.origin, s);
|
||||
|
||||
pa_rtp_context_init_recv(&s->rtp_context, fd, pa_frame_size(&s->sdp_info.sample_spec));
|
||||
|
||||
pa_log_info("Found new session '%s'", s->sdp_info.session_name);
|
||||
|
||||
pa_hashmap_put(s->userdata->by_origin, s->sdp_info.origin, s);
|
||||
u->n_sessions++;
|
||||
PA_LLIST_PREPEND(struct session, s->userdata->sessions, s);
|
||||
|
||||
pa_sink_input_put(s->sink_input);
|
||||
|
||||
pa_log_info("New session '%s'", s->sdp_info.session_name);
|
||||
|
||||
return s;
|
||||
|
||||
fail:
|
||||
if (s) {
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
pa_xfree(s);
|
||||
|
||||
pa_xfree(s);
|
||||
}
|
||||
if (fd >= 0)
|
||||
pa_close(fd);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void session_free(struct session *s, int from_hash) {
|
||||
assert(s);
|
||||
static void session_free(struct session *s) {
|
||||
pa_assert(s);
|
||||
|
||||
pa_log_info("Freeing session '%s'", s->sdp_info.session_name);
|
||||
|
||||
s->userdata->core->mainloop->time_free(s->death_event);
|
||||
s->userdata->core->mainloop->io_free(s->rtp_event);
|
||||
|
||||
if (from_hash)
|
||||
pa_hashmap_remove(s->userdata->by_origin, s->sdp_info.origin);
|
||||
|
||||
pa_sink_input_disconnect(s->sink_input);
|
||||
pa_sink_input_unlink(s->sink_input);
|
||||
pa_sink_input_unref(s->sink_input);
|
||||
|
||||
PA_LLIST_REMOVE(struct session, s->userdata->sessions, s);
|
||||
pa_assert(s->userdata->n_sessions >= 1);
|
||||
s->userdata->n_sessions--;
|
||||
pa_hashmap_remove(s->userdata->by_origin, s->sdp_info.origin);
|
||||
|
||||
pa_memblockq_free(s->memblockq);
|
||||
pa_sdp_info_destroy(&s->sdp_info);
|
||||
pa_rtp_context_destroy(&s->rtp_context);
|
||||
|
||||
assert(s->userdata->n_sessions >= 1);
|
||||
s->userdata->n_sessions--;
|
||||
|
||||
pa_xfree(s);
|
||||
}
|
||||
|
||||
|
|
@ -387,11 +437,11 @@ static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event
|
|||
pa_sdp_info info;
|
||||
struct session *s;
|
||||
|
||||
assert(m);
|
||||
assert(e);
|
||||
assert(u);
|
||||
assert(fd == u->sap_context.fd);
|
||||
assert(flags == PA_IO_EVENT_INPUT);
|
||||
pa_assert(m);
|
||||
pa_assert(e);
|
||||
pa_assert(u);
|
||||
pa_assert(fd == u->sap_context.fd);
|
||||
pa_assert(flags == PA_IO_EVENT_INPUT);
|
||||
|
||||
if (pa_sap_recv(&u->sap_context, &goodbye) < 0)
|
||||
return;
|
||||
|
|
@ -402,7 +452,7 @@ static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event
|
|||
if (goodbye) {
|
||||
|
||||
if ((s = pa_hashmap_get(u->by_origin, info.origin)))
|
||||
session_free(s, 1);
|
||||
session_free(s);
|
||||
|
||||
pa_sdp_info_destroy(&info);
|
||||
} else {
|
||||
|
|
@ -412,18 +462,47 @@ static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event
|
|||
pa_sdp_info_destroy(&info);
|
||||
|
||||
} else {
|
||||
struct timeval tv;
|
||||
|
||||
pa_gettimeofday(&tv);
|
||||
pa_timeval_add(&tv, DEATH_TIMEOUT);
|
||||
m->time_restart(s->death_event, &tv);
|
||||
struct timeval now;
|
||||
pa_rtclock_get(&now);
|
||||
pa_atomic_store(&s->timestamp, now.tv_sec);
|
||||
|
||||
pa_sdp_info_destroy(&info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
static void check_death_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *ptv, void *userdata) {
|
||||
struct session *s, *n;
|
||||
struct userdata *u = userdata;
|
||||
struct timeval now;
|
||||
struct timeval tv;
|
||||
|
||||
pa_assert(m);
|
||||
pa_assert(t);
|
||||
pa_assert(ptv);
|
||||
pa_assert(u);
|
||||
|
||||
pa_rtclock_get(&now);
|
||||
|
||||
pa_log_debug("Checking for dead streams ...");
|
||||
|
||||
for (s = u->sessions; s; s = n) {
|
||||
int k;
|
||||
n = s->next;
|
||||
|
||||
k = pa_atomic_load(&s->timestamp);
|
||||
|
||||
if (k + DEATH_TIMEOUT < now.tv_sec)
|
||||
session_free(s);
|
||||
}
|
||||
|
||||
/* Restart timer */
|
||||
pa_gettimeofday(&tv);
|
||||
pa_timeval_add(&tv, DEATH_TIMEOUT*PA_USEC_PER_SEC);
|
||||
m->time_restart(t, &tv);
|
||||
}
|
||||
|
||||
int pa__init(pa_module*m) {
|
||||
struct userdata *u;
|
||||
pa_modargs *ma = NULL;
|
||||
struct sockaddr_in sa4;
|
||||
|
|
@ -432,9 +511,9 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
socklen_t salen;
|
||||
const char *sap_address;
|
||||
int fd = -1;
|
||||
struct timeval tv;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("failed to parse module arguments");
|
||||
|
|
@ -454,7 +533,7 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
sa = (struct sockaddr*) &sa4;
|
||||
salen = sizeof(sa4);
|
||||
} else {
|
||||
pa_log("invalid SAP address '%s'", sap_address);
|
||||
pa_log("Invalid SAP address '%s'", sap_address);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -464,15 +543,18 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
u = pa_xnew(struct userdata, 1);
|
||||
m->userdata = u;
|
||||
u->module = m;
|
||||
u->core = c;
|
||||
u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));
|
||||
|
||||
u->sap_event = m->core->mainloop->io_new(m->core->mainloop, fd, PA_IO_EVENT_INPUT, sap_event_cb, u);
|
||||
pa_sap_context_init_recv(&u->sap_context, fd);
|
||||
|
||||
PA_LLIST_HEAD_INIT(struct session, u->sessions);
|
||||
u->n_sessions = 0;
|
||||
|
||||
u->sap_event = c->mainloop->io_new(c->mainloop, fd, PA_IO_EVENT_INPUT, sap_event_cb, u);
|
||||
|
||||
u->by_origin = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
|
||||
|
||||
pa_sap_context_init_recv(&u->sap_context, fd);
|
||||
pa_gettimeofday(&tv);
|
||||
pa_timeval_add(&tv, DEATH_TIMEOUT * PA_USEC_PER_SEC);
|
||||
u->check_death_event = m->core->mainloop->time_new(m->core->mainloop, &tv, check_death_event_cb, u);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
|
||||
|
|
@ -483,27 +565,34 @@ fail:
|
|||
pa_modargs_free(ma);
|
||||
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
pa_close(fd);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void free_func(void *p, PA_GCC_UNUSED void *userdata) {
|
||||
session_free(p, 0);
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
assert(c);
|
||||
assert(m);
|
||||
struct session *s;
|
||||
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
||||
c->mainloop->io_free(u->sap_event);
|
||||
if (u->sap_event)
|
||||
m->core->mainloop->io_free(u->sap_event);
|
||||
|
||||
if (u->check_death_event)
|
||||
m->core->mainloop->time_free(u->check_death_event);
|
||||
|
||||
pa_sap_context_destroy(&u->sap_context);
|
||||
|
||||
pa_hashmap_free(u->by_origin, free_func, NULL);
|
||||
if (u->by_origin) {
|
||||
while ((s = pa_hashmap_get_first(u->by_origin)))
|
||||
session_free(s);
|
||||
|
||||
pa_hashmap_free(u->by_origin, NULL, NULL);
|
||||
}
|
||||
|
||||
pa_xfree(u->sink_name);
|
||||
pa_xfree(u);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
|
|
@ -48,6 +47,9 @@
|
|||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/modargs.h>
|
||||
#include <pulsecore/namereg.h>
|
||||
#include <pulsecore/sample-util.h>
|
||||
#include <pulsecore/macro.h>
|
||||
#include <pulsecore/socket-util.h>
|
||||
|
||||
#include "module-rtp-send-symdef.h"
|
||||
|
||||
|
|
@ -74,7 +76,7 @@ PA_MODULE_USAGE(
|
|||
#define DEFAULT_DESTINATION "224.0.0.56"
|
||||
#define MEMBLOCKQ_MAXLENGTH (1024*170)
|
||||
#define DEFAULT_MTU 1280
|
||||
#define SAP_INTERVAL 5000000
|
||||
#define SAP_INTERVAL 5
|
||||
|
||||
static const char* const valid_modargs[] = {
|
||||
"source",
|
||||
|
|
@ -90,7 +92,6 @@ static const char* const valid_modargs[] = {
|
|||
|
||||
struct userdata {
|
||||
pa_module *module;
|
||||
pa_core *core;
|
||||
|
||||
pa_source_output *source_output;
|
||||
pa_memblockq *memblockq;
|
||||
|
|
@ -102,56 +103,67 @@ struct userdata {
|
|||
pa_time_event *sap_event;
|
||||
};
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static int source_output_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
|
||||
struct userdata *u;
|
||||
pa_assert_se(u = PA_SOURCE_OUTPUT(o)->userdata);
|
||||
|
||||
switch (code) {
|
||||
case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY:
|
||||
*((pa_usec_t*) data) = pa_bytes_to_usec(pa_memblockq_get_length(u->memblockq), &u->source_output->sample_spec);
|
||||
|
||||
/* Fall through, the default handler will add in the extra
|
||||
* latency added by the resampler */
|
||||
break;
|
||||
}
|
||||
|
||||
return pa_source_output_process_msg(o, code, data, offset, chunk);
|
||||
}
|
||||
|
||||
/* Called from I/O thread context */
|
||||
static void source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
|
||||
struct userdata *u;
|
||||
assert(o);
|
||||
u = o->userdata;
|
||||
pa_source_output_assert_ref(o);
|
||||
pa_assert_se(u = o->userdata);
|
||||
|
||||
if (pa_memblockq_push(u->memblockq, chunk) < 0) {
|
||||
pa_log("Failed to push chunk into memblockq.");
|
||||
pa_log_warn("Failed to push chunk into memblockq.");
|
||||
return;
|
||||
}
|
||||
|
||||
pa_rtp_send(&u->rtp_context, u->mtu, u->memblockq);
|
||||
}
|
||||
|
||||
/* Called from main context */
|
||||
static void source_output_kill(pa_source_output* o) {
|
||||
struct userdata *u;
|
||||
assert(o);
|
||||
u = o->userdata;
|
||||
pa_source_output_assert_ref(o);
|
||||
pa_assert_se(u = o->userdata);
|
||||
|
||||
pa_module_unload_request(u->module);
|
||||
|
||||
pa_source_output_disconnect(u->source_output);
|
||||
pa_source_output_unlink(u->source_output);
|
||||
pa_source_output_unref(u->source_output);
|
||||
u->source_output = NULL;
|
||||
}
|
||||
|
||||
static pa_usec_t source_output_get_latency (pa_source_output *o) {
|
||||
struct userdata *u;
|
||||
assert(o);
|
||||
u = o->userdata;
|
||||
|
||||
return pa_bytes_to_usec(pa_memblockq_get_length(u->memblockq), &o->sample_spec);
|
||||
}
|
||||
|
||||
static void sap_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *tv, void *userdata) {
|
||||
struct userdata *u = userdata;
|
||||
struct timeval next;
|
||||
|
||||
assert(m);
|
||||
assert(t);
|
||||
assert(tv);
|
||||
assert(u);
|
||||
pa_assert(m);
|
||||
pa_assert(t);
|
||||
pa_assert(tv);
|
||||
pa_assert(u);
|
||||
|
||||
pa_sap_send(&u->sap_context, 0);
|
||||
|
||||
pa_gettimeofday(&next);
|
||||
pa_timeval_add(&next, SAP_INTERVAL);
|
||||
pa_timeval_add(&next, SAP_INTERVAL * PA_USEC_PER_SEC);
|
||||
m->time_restart(t, &next);
|
||||
}
|
||||
|
||||
int pa__init(pa_core *c, pa_module*m) {
|
||||
int pa__init(pa_module*m) {
|
||||
struct userdata *u;
|
||||
pa_modargs *ma = NULL;
|
||||
const char *dest;
|
||||
|
|
@ -173,21 +185,20 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
int loop = 0;
|
||||
pa_source_output_new_data data;
|
||||
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
|
||||
pa_log("failed to parse module arguments");
|
||||
pa_log("Failed to parse module arguments");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!(s = pa_namereg_get(m->core, pa_modargs_get_value(ma, "source", NULL), PA_NAMEREG_SOURCE, 1))) {
|
||||
pa_log("source does not exist.");
|
||||
pa_log("Source does not exist.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (pa_modargs_get_value_boolean(ma, "loop", &loop) < 0) {
|
||||
pa_log("failed to parse \"loop\" parameter.");
|
||||
pa_log("Failed to parse \"loop\" parameter.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -195,12 +206,12 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
pa_rtp_sample_spec_fixup(&ss);
|
||||
cm = s->channel_map;
|
||||
if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
|
||||
pa_log("failed to parse sample specification");
|
||||
pa_log("Failed to parse sample specification");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!pa_rtp_sample_spec_valid(&ss)) {
|
||||
pa_log("specified sample type not compatible with RTP");
|
||||
pa_log("Specified sample type not compatible with RTP");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -209,10 +220,10 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
|
||||
payload = pa_rtp_payload_from_sample_spec(&ss);
|
||||
|
||||
mtu = (DEFAULT_MTU/pa_frame_size(&ss))*pa_frame_size(&ss);
|
||||
mtu = pa_frame_align(DEFAULT_MTU, &ss);
|
||||
|
||||
if (pa_modargs_get_value_u32(ma, "mtu", &mtu) < 0 || mtu < 1 || mtu % pa_frame_size(&ss) != 0) {
|
||||
pa_log("invalid mtu.");
|
||||
pa_log("Invalid MTU.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -223,7 +234,7 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
}
|
||||
|
||||
if (port & 1)
|
||||
pa_log_warn("WARNING: port number not even as suggested in RFC3550!");
|
||||
pa_log_warn("Port number not even as suggested in RFC3550!");
|
||||
|
||||
dest = pa_modargs_get_value(ma, "destination", DEFAULT_DESTINATION);
|
||||
|
||||
|
|
@ -238,7 +249,7 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
sap_sa4 = sa4;
|
||||
sap_sa4.sin_port = htons(SAP_PORT);
|
||||
} else {
|
||||
pa_log("invalid destination '%s'", dest);
|
||||
pa_log("Invalid destination '%s'", dest);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -268,6 +279,12 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
goto fail;
|
||||
}
|
||||
|
||||
/* If the socket queue is full, let's drop packets */
|
||||
pa_make_fd_nonblock(fd);
|
||||
pa_make_udp_socket_low_delay(fd);
|
||||
pa_make_fd_cloexec(fd);
|
||||
pa_make_fd_cloexec(sap_fd);
|
||||
|
||||
pa_source_output_new_data_init(&data);
|
||||
data.name = "RTP Monitor Stream";
|
||||
data.driver = __FILE__;
|
||||
|
|
@ -276,21 +293,20 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
pa_source_output_new_data_set_sample_spec(&data, &ss);
|
||||
pa_source_output_new_data_set_channel_map(&data, &cm);
|
||||
|
||||
if (!(o = pa_source_output_new(c, &data, 0))) {
|
||||
if (!(o = pa_source_output_new(m->core, &data, 0))) {
|
||||
pa_log("failed to create source output.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
o->parent.process_msg = source_output_process_msg;
|
||||
o->push = source_output_push;
|
||||
o->kill = source_output_kill;
|
||||
o->get_latency = source_output_get_latency;
|
||||
|
||||
u = pa_xnew(struct userdata, 1);
|
||||
m->userdata = u;
|
||||
o->userdata = u;
|
||||
|
||||
u->module = m;
|
||||
u->core = c;
|
||||
u->source_output = o;
|
||||
|
||||
u->memblockq = pa_memblockq_new(
|
||||
|
|
@ -305,8 +321,7 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
u->mtu = mtu;
|
||||
|
||||
k = sizeof(sa_dst);
|
||||
r = getsockname(fd, (struct sockaddr*) &sa_dst, &k);
|
||||
assert(r >= 0);
|
||||
pa_assert_se((r = getsockname(fd, (struct sockaddr*) &sa_dst, &k)) >= 0);
|
||||
|
||||
n = pa_sprintf_malloc("PulseAudio RTP Stream on %s", pa_get_fqdn(hn, sizeof(hn)));
|
||||
|
||||
|
|
@ -317,17 +332,19 @@ int pa__init(pa_core *c, pa_module*m) {
|
|||
|
||||
pa_xfree(n);
|
||||
|
||||
pa_rtp_context_init_send(&u->rtp_context, fd, c->cookie, payload, pa_frame_size(&ss));
|
||||
pa_rtp_context_init_send(&u->rtp_context, fd, m->core->cookie, payload, pa_frame_size(&ss));
|
||||
pa_sap_context_init_send(&u->sap_context, sap_fd, p);
|
||||
|
||||
pa_log_info("RTP stream initialized with mtu %u on %s:%u, SSRC=0x%08x, payload=%u, initial sequence #%u", mtu, dest, port, u->rtp_context.ssrc, payload, u->rtp_context.sequence);
|
||||
pa_log_info("SDP-Data:\n%s\n"__FILE__": EOF", p);
|
||||
pa_log_info("SDP-Data:\n%s\nEOF", p);
|
||||
|
||||
pa_sap_send(&u->sap_context, 0);
|
||||
|
||||
pa_gettimeofday(&tv);
|
||||
pa_timeval_add(&tv, SAP_INTERVAL);
|
||||
u->sap_event = c->mainloop->time_new(c->mainloop, &tv, sap_event_cb, u);
|
||||
pa_timeval_add(&tv, SAP_INTERVAL * PA_USEC_PER_SEC);
|
||||
u->sap_event = m->core->mainloop->time_new(m->core->mainloop, &tv, sap_event_cb, u);
|
||||
|
||||
pa_source_output_put(u->source_output);
|
||||
|
||||
pa_modargs_free(ma);
|
||||
|
||||
|
|
@ -338,31 +355,31 @@ fail:
|
|||
pa_modargs_free(ma);
|
||||
|
||||
if (fd >= 0)
|
||||
close(fd);
|
||||
pa_close(fd);
|
||||
|
||||
if (sap_fd >= 0)
|
||||
close(sap_fd);
|
||||
pa_close(sap_fd);
|
||||
|
||||
if (o) {
|
||||
pa_source_output_disconnect(o);
|
||||
pa_source_output_unlink(o);
|
||||
pa_source_output_unref(o);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pa__done(pa_core *c, pa_module*m) {
|
||||
void pa__done(pa_module*m) {
|
||||
struct userdata *u;
|
||||
assert(c);
|
||||
assert(m);
|
||||
pa_assert(m);
|
||||
|
||||
if (!(u = m->userdata))
|
||||
return;
|
||||
|
||||
c->mainloop->time_free(u->sap_event);
|
||||
if (u->sap_event)
|
||||
m->core->mainloop->time_free(u->sap_event);
|
||||
|
||||
if (u->source_output) {
|
||||
pa_source_output_disconnect(u->source_output);
|
||||
pa_source_output_unlink(u->source_output);
|
||||
pa_source_output_unref(u->source_output);
|
||||
}
|
||||
|
||||
|
|
@ -371,7 +388,8 @@ void pa__done(pa_core *c, pa_module*m) {
|
|||
pa_sap_send(&u->sap_context, 1);
|
||||
pa_sap_context_destroy(&u->sap_context);
|
||||
|
||||
pa_memblockq_free(u->memblockq);
|
||||
if (u->memblockq)
|
||||
pa_memblockq_free(u->memblockq);
|
||||
|
||||
pa_xfree(u);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -40,12 +39,14 @@
|
|||
|
||||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/macro.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
|
||||
#include "rtp.h"
|
||||
|
||||
pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssrc, uint8_t payload, size_t frame_size) {
|
||||
assert(c);
|
||||
assert(fd >= 0);
|
||||
pa_assert(c);
|
||||
pa_assert(fd >= 0);
|
||||
|
||||
c->fd = fd;
|
||||
c->sequence = (uint16_t) (rand()*rand());
|
||||
|
|
@ -63,11 +64,11 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) {
|
|||
struct iovec iov[MAX_IOVECS];
|
||||
pa_memblock* mb[MAX_IOVECS];
|
||||
int iov_idx = 1;
|
||||
size_t n = 0, skip = 0;
|
||||
size_t n = 0;
|
||||
|
||||
assert(c);
|
||||
assert(size > 0);
|
||||
assert(q);
|
||||
pa_assert(c);
|
||||
pa_assert(size > 0);
|
||||
pa_assert(q);
|
||||
|
||||
if (pa_memblockq_get_length(q) < size)
|
||||
return 0;
|
||||
|
|
@ -76,24 +77,26 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) {
|
|||
int r;
|
||||
pa_memchunk chunk;
|
||||
|
||||
pa_memchunk_reset(&chunk);
|
||||
|
||||
if ((r = pa_memblockq_peek(q, &chunk)) >= 0) {
|
||||
|
||||
size_t k = n + chunk.length > size ? size - n : chunk.length;
|
||||
|
||||
if (chunk.memblock) {
|
||||
iov[iov_idx].iov_base = (void*)((uint8_t*) chunk.memblock->data + chunk.index);
|
||||
iov[iov_idx].iov_len = k;
|
||||
mb[iov_idx] = chunk.memblock;
|
||||
iov_idx ++;
|
||||
pa_assert(chunk.memblock);
|
||||
|
||||
n += k;
|
||||
}
|
||||
iov[iov_idx].iov_base = ((uint8_t*) pa_memblock_acquire(chunk.memblock) + chunk.index);
|
||||
iov[iov_idx].iov_len = k;
|
||||
mb[iov_idx] = chunk.memblock;
|
||||
iov_idx ++;
|
||||
|
||||
skip += k;
|
||||
pa_memblockq_drop(q, &chunk, k);
|
||||
n += k;
|
||||
pa_memblockq_drop(q, k);
|
||||
}
|
||||
|
||||
if (r < 0 || !chunk.memblock || n >= size || iov_idx >= MAX_IOVECS) {
|
||||
pa_assert(n % c->frame_size == 0);
|
||||
|
||||
if (r < 0 || n >= size || iov_idx >= MAX_IOVECS) {
|
||||
uint32_t header[3];
|
||||
struct msghdr m;
|
||||
int k, i;
|
||||
|
|
@ -116,17 +119,19 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) {
|
|||
|
||||
k = sendmsg(c->fd, &m, MSG_DONTWAIT);
|
||||
|
||||
for (i = 1; i < iov_idx; i++)
|
||||
for (i = 1; i < iov_idx; i++) {
|
||||
pa_memblock_release(mb[i]);
|
||||
pa_memblock_unref(mb[i]);
|
||||
}
|
||||
|
||||
c->sequence++;
|
||||
} else
|
||||
k = 0;
|
||||
|
||||
c->timestamp += skip/c->frame_size;
|
||||
c->timestamp += n/c->frame_size;
|
||||
|
||||
if (k < 0) {
|
||||
if (errno != EAGAIN) /* If the queue is full, just ignore it */
|
||||
if (errno != EAGAIN && errno != EINTR) /* If the queue is full, just ignore it */
|
||||
pa_log("sendmsg() failed: %s", pa_cstrerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -135,7 +140,6 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) {
|
|||
break;
|
||||
|
||||
n = 0;
|
||||
skip = 0;
|
||||
iov_idx = 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -144,7 +148,7 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) {
|
|||
}
|
||||
|
||||
pa_rtp_context* pa_rtp_context_init_recv(pa_rtp_context *c, int fd, size_t frame_size) {
|
||||
assert(c);
|
||||
pa_assert(c);
|
||||
|
||||
c->fd = fd;
|
||||
c->frame_size = frame_size;
|
||||
|
|
@ -159,13 +163,13 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) {
|
|||
int cc;
|
||||
ssize_t r;
|
||||
|
||||
assert(c);
|
||||
assert(chunk);
|
||||
pa_assert(c);
|
||||
pa_assert(chunk);
|
||||
|
||||
chunk->memblock = NULL;
|
||||
pa_memchunk_reset(chunk);
|
||||
|
||||
if (ioctl(c->fd, FIONREAD, &size) < 0) {
|
||||
pa_log("FIONREAD failed: %s", pa_cstrerror(errno));
|
||||
pa_log_warn("FIONREAD failed: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +178,7 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) {
|
|||
|
||||
chunk->memblock = pa_memblock_new(pool, size);
|
||||
|
||||
iov.iov_base = chunk->memblock->data;
|
||||
iov.iov_base = pa_memblock_acquire(chunk->memblock);
|
||||
iov.iov_len = size;
|
||||
|
||||
m.msg_name = NULL;
|
||||
|
|
@ -185,36 +189,41 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) {
|
|||
m.msg_controllen = 0;
|
||||
m.msg_flags = 0;
|
||||
|
||||
if ((r = recvmsg(c->fd, &m, 0)) != size) {
|
||||
pa_log("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch");
|
||||
r = recvmsg(c->fd, &m, 0);
|
||||
pa_memblock_release(chunk->memblock);
|
||||
|
||||
if (r != size) {
|
||||
if (r < 0 && errno != EAGAIN && errno != EINTR)
|
||||
pa_log_warn("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch");
|
||||
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (size < 12) {
|
||||
pa_log("RTP packet too short.");
|
||||
pa_log_warn("RTP packet too short.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
memcpy(&header, chunk->memblock->data, sizeof(uint32_t));
|
||||
memcpy(&c->timestamp, (uint8_t*) chunk->memblock->data + 4, sizeof(uint32_t));
|
||||
memcpy(&c->ssrc, (uint8_t*) chunk->memblock->data + 8, sizeof(uint32_t));
|
||||
memcpy(&header, iov.iov_base, sizeof(uint32_t));
|
||||
memcpy(&c->timestamp, (uint8_t*) iov.iov_base + 4, sizeof(uint32_t));
|
||||
memcpy(&c->ssrc, (uint8_t*) iov.iov_base + 8, sizeof(uint32_t));
|
||||
|
||||
header = ntohl(header);
|
||||
c->timestamp = ntohl(c->timestamp);
|
||||
c->ssrc = ntohl(c->ssrc);
|
||||
|
||||
if ((header >> 30) != 2) {
|
||||
pa_log("Unsupported RTP version.");
|
||||
pa_log_warn("Unsupported RTP version.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ((header >> 29) & 1) {
|
||||
pa_log("RTP padding not supported.");
|
||||
pa_log_warn("RTP padding not supported.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ((header >> 28) & 1) {
|
||||
pa_log("RTP header extensions not supported.");
|
||||
pa_log_warn("RTP header extensions not supported.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -223,7 +232,7 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) {
|
|||
c->sequence = header & 0xFFFF;
|
||||
|
||||
if (12 + cc*4 > size) {
|
||||
pa_log("RTP packet too short. (CSRC)");
|
||||
pa_log_warn("RTP packet too short. (CSRC)");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -231,7 +240,7 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) {
|
|||
chunk->length = size - chunk->index;
|
||||
|
||||
if (chunk->length % c->frame_size != 0) {
|
||||
pa_log("Vad RTP packet size.");
|
||||
pa_log_warn("Bad RTP packet size.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -245,7 +254,7 @@ fail:
|
|||
}
|
||||
|
||||
uint8_t pa_rtp_payload_from_sample_spec(const pa_sample_spec *ss) {
|
||||
assert(ss);
|
||||
pa_assert(ss);
|
||||
|
||||
if (ss->format == PA_SAMPLE_ULAW && ss->rate == 8000 && ss->channels == 1)
|
||||
return 0;
|
||||
|
|
@ -260,7 +269,7 @@ uint8_t pa_rtp_payload_from_sample_spec(const pa_sample_spec *ss) {
|
|||
}
|
||||
|
||||
pa_sample_spec *pa_rtp_sample_spec_from_payload(uint8_t payload, pa_sample_spec *ss) {
|
||||
assert(ss);
|
||||
pa_assert(ss);
|
||||
|
||||
switch (payload) {
|
||||
case 0:
|
||||
|
|
@ -295,17 +304,17 @@ pa_sample_spec *pa_rtp_sample_spec_from_payload(uint8_t payload, pa_sample_spec
|
|||
}
|
||||
|
||||
pa_sample_spec *pa_rtp_sample_spec_fixup(pa_sample_spec * ss) {
|
||||
assert(ss);
|
||||
pa_assert(ss);
|
||||
|
||||
if (!pa_rtp_sample_spec_valid(ss))
|
||||
ss->format = PA_SAMPLE_S16BE;
|
||||
|
||||
assert(pa_rtp_sample_spec_valid(ss));
|
||||
pa_assert(pa_rtp_sample_spec_valid(ss));
|
||||
return ss;
|
||||
}
|
||||
|
||||
int pa_rtp_sample_spec_valid(const pa_sample_spec *ss) {
|
||||
assert(ss);
|
||||
pa_assert(ss);
|
||||
|
||||
if (!pa_sample_spec_valid(ss))
|
||||
return 0;
|
||||
|
|
@ -318,9 +327,9 @@ int pa_rtp_sample_spec_valid(const pa_sample_spec *ss) {
|
|||
}
|
||||
|
||||
void pa_rtp_context_destroy(pa_rtp_context *c) {
|
||||
assert(c);
|
||||
pa_assert(c);
|
||||
|
||||
close(c->fd);
|
||||
pa_close(c->fd);
|
||||
}
|
||||
|
||||
const char* pa_rtp_format_to_string(pa_sample_format_t f) {
|
||||
|
|
@ -339,7 +348,7 @@ const char* pa_rtp_format_to_string(pa_sample_format_t f) {
|
|||
}
|
||||
|
||||
pa_sample_format_t pa_rtp_string_to_format(const char *s) {
|
||||
assert(s);
|
||||
pa_assert(s);
|
||||
|
||||
if (!(strcmp(s, "L16")))
|
||||
return PA_SAMPLE_S16BE;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
|
|
@ -46,6 +45,7 @@
|
|||
#include <pulsecore/core-error.h>
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/macro.h>
|
||||
|
||||
#include "sap.h"
|
||||
#include "sdp.h"
|
||||
|
|
@ -53,9 +53,9 @@
|
|||
#define MIME_TYPE "application/sdp"
|
||||
|
||||
pa_sap_context* pa_sap_context_init_send(pa_sap_context *c, int fd, char *sdp_data) {
|
||||
assert(c);
|
||||
assert(fd >= 0);
|
||||
assert(sdp_data);
|
||||
pa_assert(c);
|
||||
pa_assert(fd >= 0);
|
||||
pa_assert(sdp_data);
|
||||
|
||||
c->fd = fd;
|
||||
c->sdp_data = sdp_data;
|
||||
|
|
@ -65,9 +65,9 @@ pa_sap_context* pa_sap_context_init_send(pa_sap_context *c, int fd, char *sdp_da
|
|||
}
|
||||
|
||||
void pa_sap_context_destroy(pa_sap_context *c) {
|
||||
assert(c);
|
||||
pa_assert(c);
|
||||
|
||||
close(c->fd);
|
||||
pa_close(c->fd);
|
||||
pa_xfree(c->sdp_data);
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ int pa_sap_send(pa_sap_context *c, int goodbye) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
assert(sa->sa_family == AF_INET || sa->sa_family == AF_INET6);
|
||||
pa_assert(sa->sa_family == AF_INET || sa->sa_family == AF_INET6);
|
||||
|
||||
header = htonl(((uint32_t) 1 << 29) |
|
||||
(sa->sa_family == AF_INET6 ? (uint32_t) 1 << 28 : 0) |
|
||||
|
|
@ -113,14 +113,14 @@ int pa_sap_send(pa_sap_context *c, int goodbye) {
|
|||
m.msg_flags = 0;
|
||||
|
||||
if ((k = sendmsg(c->fd, &m, MSG_DONTWAIT)) < 0)
|
||||
pa_log("sendmsg() failed: %s\n", pa_cstrerror(errno));
|
||||
pa_log_warn("sendmsg() failed: %s\n", pa_cstrerror(errno));
|
||||
|
||||
return k;
|
||||
}
|
||||
|
||||
pa_sap_context* pa_sap_context_init_recv(pa_sap_context *c, int fd) {
|
||||
assert(c);
|
||||
assert(fd >= 0);
|
||||
pa_assert(c);
|
||||
pa_assert(fd >= 0);
|
||||
|
||||
c->fd = fd;
|
||||
c->sdp_data = NULL;
|
||||
|
|
@ -136,11 +136,11 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) {
|
|||
int six, ac;
|
||||
ssize_t r;
|
||||
|
||||
assert(c);
|
||||
assert(goodbye);
|
||||
pa_assert(c);
|
||||
pa_assert(goodbye);
|
||||
|
||||
if (ioctl(c->fd, FIONREAD, &size) < 0) {
|
||||
pa_log("FIONREAD failed: %s", pa_cstrerror(errno));
|
||||
pa_log_warn("FIONREAD failed: %s", pa_cstrerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -159,12 +159,12 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) {
|
|||
m.msg_flags = 0;
|
||||
|
||||
if ((r = recvmsg(c->fd, &m, 0)) != size) {
|
||||
pa_log("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch");
|
||||
pa_log_warn("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (size < 4) {
|
||||
pa_log("SAP packet too short.");
|
||||
pa_log_warn("SAP packet too short.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -172,17 +172,17 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) {
|
|||
header = ntohl(header);
|
||||
|
||||
if (header >> 29 != 1) {
|
||||
pa_log("Unsupported SAP version.");
|
||||
pa_log_warn("Unsupported SAP version.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ((header >> 25) & 1) {
|
||||
pa_log("Encrypted SAP not supported.");
|
||||
pa_log_warn("Encrypted SAP not supported.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if ((header >> 24) & 1) {
|
||||
pa_log("Compressed SAP not supported.");
|
||||
pa_log_warn("Compressed SAP not supported.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +191,7 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) {
|
|||
|
||||
k = 4 + (six ? 16 : 4) + ac*4;
|
||||
if (size < k) {
|
||||
pa_log("SAP packet too short (AD).");
|
||||
pa_log_warn("SAP packet too short (AD).");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +202,7 @@ int pa_sap_recv(pa_sap_context *c, int *goodbye) {
|
|||
e += sizeof(MIME_TYPE);
|
||||
size -= sizeof(MIME_TYPE);
|
||||
} else if ((unsigned) size < sizeof(PA_SDP_HEADER)-1 || strncmp(e, PA_SDP_HEADER, sizeof(PA_SDP_HEADER)-1)) {
|
||||
pa_log("Invalid SDP header.");
|
||||
pa_log_warn("Invalid SDP header.");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
#include <config.h>
|
||||
#endif
|
||||
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
|
|
@ -35,36 +34,33 @@
|
|||
#include <string.h>
|
||||
|
||||
#include <pulse/xmalloc.h>
|
||||
#include <pulse/util.h>
|
||||
|
||||
#include <pulsecore/core-util.h>
|
||||
#include <pulsecore/log.h>
|
||||
#include <pulsecore/macro.h>
|
||||
|
||||
#include "sdp.h"
|
||||
#include "rtp.h"
|
||||
|
||||
|
||||
char *pa_sdp_build(int af, const void *src, const void *dst, const char *name, uint16_t port, uint8_t payload, const pa_sample_spec *ss) {
|
||||
uint32_t ntp;
|
||||
char buf_src[64], buf_dst[64];
|
||||
char buf_src[64], buf_dst[64], un[64];
|
||||
const char *u, *f, *a;
|
||||
|
||||
assert(src);
|
||||
assert(dst);
|
||||
assert(af == AF_INET || af == AF_INET6);
|
||||
pa_assert(src);
|
||||
pa_assert(dst);
|
||||
pa_assert(af == AF_INET || af == AF_INET6);
|
||||
|
||||
f = pa_rtp_format_to_string(ss->format);
|
||||
assert(f);
|
||||
pa_assert_se(f = pa_rtp_format_to_string(ss->format));
|
||||
|
||||
if (!(u = getenv("USER")))
|
||||
if (!(u = getenv("USERNAME")))
|
||||
u = "-";
|
||||
if (!(u = pa_get_user_name(un, sizeof(un))))
|
||||
u = "-";
|
||||
|
||||
ntp = time(NULL) + 2208988800U;
|
||||
|
||||
a = inet_ntop(af, src, buf_src, sizeof(buf_src));
|
||||
assert(a);
|
||||
a = inet_ntop(af, dst, buf_dst, sizeof(buf_dst));
|
||||
assert(a);
|
||||
pa_assert_se(a = inet_ntop(af, src, buf_src, sizeof(buf_src)));
|
||||
pa_assert_se(a = inet_ntop(af, dst, buf_dst, sizeof(buf_dst)));
|
||||
|
||||
return pa_sprintf_malloc(
|
||||
PA_SDP_HEADER
|
||||
|
|
@ -86,8 +82,8 @@ char *pa_sdp_build(int af, const void *src, const void *dst, const char *name, u
|
|||
|
||||
static pa_sample_spec *parse_sdp_sample_spec(pa_sample_spec *ss, char *c) {
|
||||
unsigned rate, channels;
|
||||
assert(ss);
|
||||
assert(c);
|
||||
pa_assert(ss);
|
||||
pa_assert(c);
|
||||
|
||||
if (pa_startswith(c, "L16/")) {
|
||||
ss->format = PA_SAMPLE_S16BE;
|
||||
|
|
@ -123,8 +119,8 @@ pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) {
|
|||
uint16_t port = 0;
|
||||
int ss_valid = 0;
|
||||
|
||||
assert(t);
|
||||
assert(i);
|
||||
pa_assert(t);
|
||||
pa_assert(i);
|
||||
|
||||
i->origin = i->session_name = NULL;
|
||||
i->salen = 0;
|
||||
|
|
@ -258,7 +254,7 @@ fail:
|
|||
}
|
||||
|
||||
void pa_sdp_info_destroy(pa_sdp_info *i) {
|
||||
assert(i);
|
||||
pa_assert(i);
|
||||
|
||||
pa_xfree(i->origin);
|
||||
pa_xfree(i->session_name);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue